├── .editorconfig ├── .gitignore ├── Caches ├── FileDbCache │ ├── FileDbCache.cs │ └── FileDbCache.csproj └── SQLiteCache │ ├── SQLiteCache.cs │ └── SQLiteCache.csproj ├── Directory.Build.props ├── LICENSE ├── MBTiles ├── Avalonia │ └── MBTiles.Avalonia.csproj ├── Shared │ ├── MBTileLayer.cs │ └── MBTileSource.cs ├── UWP │ ├── MBTiles.UWP.csproj │ └── Properties │ │ ├── AssemblyInfo.cs │ │ └── MBTiles.UWP.rd.xml ├── WPF │ └── MBTiles.WPF.csproj └── WinUI │ └── MBTiles.WinUI.csproj ├── MapControl.sln ├── MapControl.snk ├── MapControl ├── Avalonia │ ├── DependencyPropertyHelper.Avalonia.cs │ ├── GeoImage.Avalonia.cs │ ├── ImageLoader.Avalonia.cs │ ├── LocationAnimator.Avalonia.cs │ ├── Map.Avalonia.cs │ ├── MapBase.Avalonia.cs │ ├── MapContentControl.Avalonia.cs │ ├── MapControl.Avalonia.csproj │ ├── MapGraticule.Avalonia.cs │ ├── MapImageLayer.Avalonia.cs │ ├── MapItem.Avalonia.cs │ ├── MapItemsControl.Avalonia.cs │ ├── MapPanel.Avalonia.cs │ ├── MapPath.Avalonia.cs │ ├── MapPolypoint.Avalonia.cs │ ├── PushpinBorder.Avalonia.cs │ ├── Themes │ │ └── Generic.axaml │ ├── Tile.Avalonia.cs │ ├── TileImageLoader.Avalonia.cs │ └── ViewTransform.Avalonia.cs ├── Shared │ ├── AutoEquirectangularProjection.cs │ ├── AzimuthalEquidistantProjection.cs │ ├── AzimuthalProjection.cs │ ├── BingMapsTileLayer.cs │ ├── BingMapsTileSource.cs │ ├── BoundingBox.cs │ ├── BoundingBoxTileSource.cs │ ├── CenteredBoundingBox.cs │ ├── DispatcherTimerHelper.cs │ ├── EquirectangularProjection.cs │ ├── Etrs89UtmProjection.cs │ ├── FilePath.cs │ ├── GeoImage.cs │ ├── GnomonicProjection.cs │ ├── GroundOverlay.cs │ ├── ImageFileCache.cs │ ├── ImageLoader.cs │ ├── LatLonBox.cs │ ├── Location.cs │ ├── LocationCollection.cs │ ├── MapBase.cs │ ├── MapBorderPanel.cs │ ├── MapGraticule.cs │ ├── MapImageLayer.cs │ ├── MapItem.cs │ ├── MapItemsControl.cs │ ├── MapMultiPolygon.cs │ ├── MapOverlaysPanel.cs │ ├── MapPanel.cs │ ├── MapPath.cs │ ├── MapPolygon.cs │ ├── MapPolyline.cs │ ├── MapProjection.cs │ ├── MapProjectionFactory.cs │ ├── MapScale.cs │ ├── MapTileLayer.cs │ ├── MapTileLayerBase.cs │ ├── Nad27UtmProjection.cs │ ├── Nad83UtmProjection.cs │ ├── OrthographicProjection.cs │ ├── PolarStereographicProjection.cs │ ├── PolygonCollection.cs │ ├── PushpinBorder.cs │ ├── StereographicProjection.cs │ ├── Tile.cs │ ├── TileCollection.cs │ ├── TileImageLoader.cs │ ├── TileMatrix.cs │ ├── TileSource.cs │ ├── TransverseMercatorProjection.cs │ ├── TypeConverters.cs │ ├── ViewTransform.cs │ ├── ViewportChangedEventArgs.cs │ ├── WebMercatorProjection.cs │ ├── Wgs84UtmProjection.cs │ ├── WmsImageLayer.cs │ ├── WmtsCapabilities.cs │ ├── WmtsTileLayer.cs │ ├── WmtsTileMatrix.cs │ ├── WmtsTileMatrixLayer.cs │ ├── WmtsTileMatrixSet.cs │ ├── WmtsTileSource.cs │ └── WorldMercatorProjection.cs ├── UWP │ ├── MapControl.UWP.csproj │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── MapControl.UWP.rd.xml │ ├── Themes │ │ └── Generic.xaml │ └── TileImageLoader.UWP.cs ├── WPF │ ├── AssemblyInfo.cs │ ├── DependencyPropertyHelper.WPF.cs │ ├── GeoImage.WPF.cs │ ├── ImageLoader.WPF.cs │ ├── LocationAnimation.WPF.cs │ ├── Map.WPF.cs │ ├── MapBase.WPF.cs │ ├── MapContentControl.WPF.cs │ ├── MapControl.WPF.csproj │ ├── MapGraticule.WPF.cs │ ├── MapImageLayer.WPF.cs │ ├── MapItem.WPF.cs │ ├── MapItemsControl.WPF.cs │ ├── MapPanel.WPF.cs │ ├── MapPath.WPF.cs │ ├── MapPolypoint.WPF.cs │ ├── PolygonCollection.WPF.cs │ ├── PushpinBorder.WPF.cs │ ├── Themes │ │ └── Generic.xaml │ ├── Tile.WPF.cs │ └── TileImageLoader.WPF.cs └── WinUI │ ├── DependencyPropertyHelper.WinUI.cs │ ├── GeoImage.WinUI.cs │ ├── ImageLoader.WinUI.cs │ ├── Map.WinUI.cs │ ├── MapBase.WinUI.cs │ ├── MapContentControl.WinUI.cs │ ├── MapControl.WinUI.csproj │ ├── MapGraticule.WinUI.cs │ ├── MapImageLayer.WinUI.cs │ ├── MapItem.WinUI.cs │ ├── MapItemsControl.WinUI.cs │ ├── MapPanel.WinUI.cs │ ├── MapPath.WinUI.cs │ ├── MapPolypoint.WinUI.cs │ ├── Matrix.WinUI.cs │ ├── Point.WinUI.cs │ ├── PushpinBorder.WinUI.cs │ ├── Rect.WinUI.cs │ ├── Themes │ └── Generic.xaml │ ├── Tile.WinUI.cs │ └── TileImageLoader.WinUI.cs ├── MapControlExtended.sln ├── MapProjections ├── Avalonia │ └── MapProjections.Avalonia.csproj ├── Shared │ ├── Ed50UtmProjection.cs │ ├── Etrs89UtmProjection.cs │ ├── GeoApiProjection.cs │ ├── GeoApiProjectionFactory.cs │ ├── Nad27UtmProjection.cs │ ├── Nad83UtmProjection.cs │ ├── WebMercatorProjection.cs │ ├── Wgs84UtmProjection.cs │ └── WorldMercatorProjection.cs ├── UWP │ ├── MapProjections.UWP.csproj │ └── Properties │ │ ├── AssemblyInfo.cs │ │ └── MapProjections.UWP.rd.xml ├── WPF │ └── MapProjections.WPF.csproj └── WinUI │ └── MapProjections.WinUI.csproj ├── MapUiTools ├── Avalonia │ ├── MapMenuItem.Avalonia.cs │ ├── MapUiTools.Avalonia.csproj │ └── MenuButton.Avalonia.cs ├── Shared │ ├── MapLayerMenuItem.cs │ ├── MapProjectionMenuItem.cs │ └── MenuButton.cs ├── UWP │ ├── MapUiTools.UWP.csproj │ └── Properties │ │ ├── AssemblyInfo.cs │ │ └── MapUiTools.UWP.rd.xml ├── WPF │ ├── AssemblyInfo.cs │ ├── MapMenuItem.WPF.cs │ ├── MapUiTools.WPF.csproj │ ├── MenuButton.WPF.cs │ └── Themes │ │ └── Generic.xaml └── WinUI │ ├── MapMenuItem.WinUI.cs │ ├── MapUiTools.WinUI.csproj │ └── MenuButton.WinUI.cs ├── README.md └── SampleApps ├── AvaloniaApp ├── App.axaml ├── App.axaml.cs ├── AvaloniaApp.csproj ├── MainWindow.axaml ├── MainWindow.axaml.cs └── Program.cs ├── ProjectionDemo ├── App.xaml ├── App.xaml.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── ProjectionDemo.csproj └── ProjectionDemo.csproj.user ├── Shared ├── 10_535_330.jpg ├── HyperlinkText.cs ├── MapViewModel.cs ├── ValueConverters.cs └── etna.kml ├── UniversalApp ├── App.xaml ├── App.xaml.cs ├── Assets │ ├── LockScreenLogo.scale-200.png │ ├── SplashScreen.scale-200.png │ ├── Square150x150Logo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── StoreLogo.png │ └── Wide310x150Logo.scale-200.png ├── MainPage.xaml ├── MainPage.xaml.cs ├── Package.appxmanifest ├── Properties │ ├── AssemblyInfo.cs │ └── Default.rd.xml ├── UniversalApp.csproj └── UniversalApp_TemporaryKey.pfx ├── WinUiApp ├── App.xaml ├── App.xaml.cs ├── Assets │ ├── LockScreenLogo.scale-200.png │ ├── SplashScreen.scale-200.png │ ├── Square150x150Logo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── StoreLogo.png │ └── Wide310x150Logo.scale-200.png ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Package.appxmanifest ├── Properties │ ├── PublishProfiles │ │ ├── win-arm64.pubxml │ │ ├── win-x64.pubxml │ │ └── win-x86.pubxml │ └── launchSettings.json ├── WinUiApp.csproj └── app.manifest └── WpfApplication ├── App.xaml ├── App.xaml.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs └── WpfApplication.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' 4 | dotnet_diagnostic.CA1835.severity = silent 5 | 6 | # CsWinRT1028: Class is not marked partial 7 | dotnet_diagnostic.CsWinRT1028.severity = silent 8 | 9 | # CsWinRT1030: Project does not enable unsafe blocks 10 | dotnet_diagnostic.CsWinRT1030.severity = silent 11 | 12 | # IDE0063: Use simple 'using' statement 13 | dotnet_diagnostic.IDE0063.severity = silent 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | *.user 5 | -------------------------------------------------------------------------------- /Caches/FileDbCache/FileDbCache.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | MapControl.Caching 5 | XAML Map Control FileDbCache Library 6 | $(GeneratePackage) 7 | XAML.MapControl.FileDbCache 8 | $(AssemblyTitle) 9 | IDistributedCache implementation for XAML Map Control, based on EzTools FileDb 10 | https://github.com/ClemensFischer/XAML-Map-Control 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Caches/SQLiteCache/SQLiteCache.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | MapControl.Caching 5 | XAML Map Control SQLiteCache Library 6 | $(GeneratePackage) 7 | XAML.MapControl.SQLiteCache 8 | $(AssemblyTitle) 9 | IDistributedCache implementation for XAML Map Control, based on SQLite 10 | https://github.com/ClemensFischer/XAML-Map-Control 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | XAML Map Control 4 | Clemens Fischer 5 | Copyright © 2025 Clemens Fischer 6 | 13.4.0 7 | $(Version) 8 | ..\..\MapControl.snk 9 | true 10 | false 11 | XAML WPF WinUI Avalonia Map OpenStreetMap 12 | MIT 13 | https://github.com/ClemensFischer/XAML-Map-Control 14 | disable 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Clemens Fischer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MBTiles/Avalonia/MBTiles.Avalonia.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | AVALONIA 5 | MapControl.MBTiles 6 | XAML Map Control MBTiles Library for Avalonia UI 7 | $(GeneratePackage) 8 | XAML.MapControl.MBTiles.Avalonia 9 | $(AssemblyTitle) 10 | MBTiles library for XAML Map Control 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MBTiles/Shared/MBTileLayer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Threading.Tasks; 4 | #if WPF 5 | using System.Windows; 6 | #elif UWP 7 | using Windows.UI.Xaml; 8 | #elif WINUI 9 | using Microsoft.UI.Xaml; 10 | #elif AVALONIA 11 | using DependencyProperty = Avalonia.AvaloniaProperty; 12 | #endif 13 | 14 | namespace MapControl.MBTiles 15 | { 16 | /// 17 | /// MapTileLayer that uses an MBTiles SQLite Database. See https://wiki.openstreetmap.org/wiki/MBTiles. 18 | /// 19 | public class MBTileLayer : MapTileLayer 20 | { 21 | public static readonly DependencyProperty FileProperty = 22 | DependencyPropertyHelper.Register(nameof(File), null, 23 | async (layer, oldValue, newValue) => await layer.FilePropertyChanged(newValue)); 24 | 25 | public string File 26 | { 27 | get => (string)GetValue(FileProperty); 28 | set => SetValue(FileProperty, value); 29 | } 30 | 31 | /// 32 | /// May be overridden to create a derived MBTileSource that handles other tile formats than png and jpg. 33 | /// 34 | protected virtual async Task CreateTileSourceAsync(string file) 35 | { 36 | var tileSource = new MBTileSource(); 37 | 38 | await tileSource.OpenAsync(file); 39 | 40 | if (tileSource.Metadata.TryGetValue("format", out string format) && format != "png" && format != "jpg") 41 | { 42 | tileSource.Dispose(); 43 | 44 | throw new NotSupportedException($"Tile image format {format} is not supported."); 45 | } 46 | 47 | return tileSource; 48 | } 49 | 50 | private async Task FilePropertyChanged(string file) 51 | { 52 | (TileSource as MBTileSource)?.Close(); 53 | 54 | ClearValue(TileSourceProperty); 55 | ClearValue(SourceNameProperty); 56 | ClearValue(DescriptionProperty); 57 | ClearValue(MinZoomLevelProperty); 58 | ClearValue(MaxZoomLevelProperty); 59 | 60 | if (!string.IsNullOrEmpty(file)) 61 | { 62 | try 63 | { 64 | var tileSource = await CreateTileSourceAsync(file); 65 | 66 | TileSource = tileSource; 67 | 68 | if (tileSource.Metadata.TryGetValue("name", out string value)) 69 | { 70 | SourceName = value; 71 | } 72 | 73 | if (tileSource.Metadata.TryGetValue("description", out value)) 74 | { 75 | Description = value; 76 | } 77 | 78 | if (tileSource.Metadata.TryGetValue("minzoom", out value) && int.TryParse(value, out int zoomLevel)) 79 | { 80 | MinZoomLevel = zoomLevel; 81 | } 82 | 83 | if (tileSource.Metadata.TryGetValue("maxzoom", out value) && int.TryParse(value, out zoomLevel)) 84 | { 85 | MaxZoomLevel = zoomLevel; 86 | } 87 | } 88 | catch (Exception ex) 89 | { 90 | ImageLoader.LoggerFactory?.CreateLogger()?.LogError(ex, "Invalid file: {file}", file); 91 | } 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /MBTiles/Shared/MBTileSource.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data.SQLite; 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | #if WPF 8 | using System.Windows.Media; 9 | #elif UWP 10 | using Windows.UI.Xaml.Media; 11 | #elif WINUI 12 | using Microsoft.UI.Xaml.Media; 13 | #elif AVALONIA 14 | using ImageSource = Avalonia.Media.IImage; 15 | #endif 16 | 17 | namespace MapControl.MBTiles 18 | { 19 | public sealed class MBTileSource : TileSource, IDisposable 20 | { 21 | private static ILogger logger; 22 | private static ILogger Logger => logger ?? (logger = ImageLoader.LoggerFactory?.CreateLogger()); 23 | 24 | private SQLiteConnection connection; 25 | 26 | public IDictionary Metadata { get; } = new Dictionary(); 27 | 28 | public async Task OpenAsync(string file) 29 | { 30 | Close(); 31 | 32 | connection = new SQLiteConnection("Data Source=" + Path.GetFullPath(file) + ";Read Only=True"); 33 | 34 | await connection.OpenAsync(); 35 | 36 | using (var command = new SQLiteCommand("select * from metadata", connection)) 37 | { 38 | var reader = await command.ExecuteReaderAsync(); 39 | 40 | while (await reader.ReadAsync()) 41 | { 42 | Metadata[(string)reader["name"]] = (string)reader["value"]; 43 | } 44 | } 45 | } 46 | 47 | public void Close() 48 | { 49 | if (connection != null) 50 | { 51 | Metadata.Clear(); 52 | connection.Dispose(); 53 | connection = null; 54 | } 55 | } 56 | 57 | public void Dispose() 58 | { 59 | Close(); 60 | } 61 | 62 | public override async Task LoadImageAsync(int x, int y, int zoomLevel) 63 | { 64 | ImageSource image = null; 65 | 66 | try 67 | { 68 | using (var command = new SQLiteCommand("select tile_data from tiles where zoom_level=@z and tile_column=@x and tile_row=@y", connection)) 69 | { 70 | command.Parameters.AddWithValue("@z", zoomLevel); 71 | command.Parameters.AddWithValue("@x", x); 72 | command.Parameters.AddWithValue("@y", (1 << zoomLevel) - y - 1); 73 | 74 | var buffer = (byte[])await command.ExecuteScalarAsync(); 75 | 76 | image = await LoadImageAsync(buffer); 77 | } 78 | } 79 | catch (Exception ex) 80 | { 81 | Logger?.LogError(ex, "LoadImageAsync"); 82 | } 83 | 84 | return image; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /MBTiles/UWP/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("XAML Map Control MBTiles Library for UWP")] 5 | [assembly: AssemblyProduct("XAML Map Control")] 6 | [assembly: AssemblyCompany("Clemens Fischer")] 7 | [assembly: AssemblyCopyright("Copyright © 2024 Clemens Fischer")] 8 | [assembly: AssemblyTrademark("")] 9 | [assembly: AssemblyVersion("13.0.0")] 10 | [assembly: AssemblyFileVersion("13.0.0")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCulture("")] 13 | [assembly: ComVisible(false)] 14 | -------------------------------------------------------------------------------- /MBTiles/UWP/Properties/MBTiles.UWP.rd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MBTiles/WPF/MBTiles.WPF.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows;net462 4 | true 5 | WPF 6 | MapControl.MBTiles 7 | XAML Map Control MBTiles Library for WPF 8 | $(GeneratePackage) 9 | XAML.MapControl.MBTiles.WPF 10 | $(AssemblyTitle) 11 | MBTiles library for XAML Map Control 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MBTiles/WinUI/MBTiles.WinUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows10.0.17763.0 4 | win-x86;win-x64;win-arm64 5 | true 6 | WINUI 7 | MapControl.MBTiles 8 | XAML Map Control MBTiles Library for WinUI 9 | $(GeneratePackage) 10 | XAML.MapControl.MBTiles.WinUI 11 | $(AssemblyTitle) 12 | MBTiles library for XAML Map Control 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /MapControl.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/MapControl.snk -------------------------------------------------------------------------------- /MapControl/Avalonia/DependencyPropertyHelper.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | #pragma warning disable AVP1001 4 | 5 | namespace MapControl 6 | { 7 | public static class DependencyPropertyHelper 8 | { 9 | public static AttachedProperty RegisterAttached( 10 | string name, 11 | Type ownerType, 12 | TValue defaultValue = default, 13 | Action changed = null, 14 | bool inherits = false) 15 | { 16 | var property = AvaloniaProperty.RegisterAttached(name, ownerType, defaultValue, inherits); 17 | 18 | if (changed != null) 19 | { 20 | property.Changed.AddClassHandler((o, e) => changed(o, e.OldValue.Value, e.NewValue.Value)); 21 | } 22 | 23 | return property; 24 | } 25 | 26 | public static StyledProperty Register( 27 | string name, 28 | TValue defaultValue = default, 29 | Action changed = null, 30 | Func coerce = null, 31 | bool bindTwoWayByDefault = false) 32 | where TOwner : AvaloniaObject 33 | { 34 | Func coerceFunc = null; 35 | 36 | if (coerce != null) 37 | { 38 | // Do not coerce default value. 39 | // 40 | coerceFunc = (obj, value) => Equals(value, defaultValue) ? value : coerce((TOwner)obj, value); 41 | } 42 | 43 | var bindingMode = bindTwoWayByDefault ? BindingMode.TwoWay : BindingMode.OneWay; 44 | 45 | var property = AvaloniaProperty.Register(name, defaultValue, false, bindingMode, null, coerceFunc); 46 | 47 | if (changed != null) 48 | { 49 | property.Changed.AddClassHandler((o, e) => changed(o, e.OldValue.Value, e.NewValue.Value)); 50 | } 51 | 52 | return property; 53 | } 54 | 55 | public static void SetBinding(this AvaloniaObject target, AvaloniaProperty property, Binding binding) 56 | { 57 | target.Bind(property, binding); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MapControl/Avalonia/GeoImage.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace MapControl 5 | { 6 | public static partial class GeoImage 7 | { 8 | private static Task LoadGeoTiff(string sourcePath) 9 | { 10 | throw new InvalidOperationException("GeoTIFF is not supported."); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MapControl/Avalonia/ImageLoader.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | 5 | namespace MapControl 6 | { 7 | public static partial class ImageLoader 8 | { 9 | public static IImage LoadImage(Uri uri) 10 | { 11 | return null; 12 | } 13 | 14 | public static IImage LoadImage(Stream stream) 15 | { 16 | return new Bitmap(stream); 17 | } 18 | 19 | public static Task LoadImageAsync(Stream stream) 20 | { 21 | return Task.FromResult(LoadImage(stream)); 22 | } 23 | 24 | public static Task LoadImageAsync(string path) 25 | { 26 | if (!File.Exists(path)) 27 | { 28 | return Task.FromResult(null); 29 | } 30 | 31 | using (var stream = File.OpenRead(path)) 32 | { 33 | return LoadImageAsync(stream); 34 | } 35 | } 36 | 37 | internal static async Task LoadMergedImageAsync(Uri uri1, Uri uri2, IProgress progress) 38 | { 39 | WriteableBitmap mergedBitmap = null; 40 | var p1 = 0d; 41 | var p2 = 0d; 42 | 43 | var images = await Task.WhenAll( 44 | LoadImageAsync(uri1, new Progress(p => { p1 = p; progress.Report((p1 + p2) / 2d); })), 45 | LoadImageAsync(uri2, new Progress(p => { p2 = p; progress.Report((p1 + p2) / 2d); }))); 46 | 47 | if (images.Length == 2 && 48 | images[0] is Bitmap bitmap1 && 49 | images[1] is Bitmap bitmap2 && 50 | bitmap1.PixelSize.Height == bitmap2.PixelSize.Height && 51 | bitmap1.Format.HasValue && 52 | bitmap1.Format == bitmap2.Format && 53 | bitmap1.AlphaFormat.HasValue && 54 | bitmap1.AlphaFormat == bitmap2.AlphaFormat) 55 | { 56 | var bpp = bitmap1.Format.Value == PixelFormat.Rgb565 ? 2 : 4; 57 | var pixelSize = new PixelSize(bitmap1.PixelSize.Width + bitmap2.PixelSize.Width, bitmap1.PixelSize.Height); 58 | var stride1 = bpp * bitmap1.PixelSize.Width; 59 | var stride = bpp * pixelSize.Width; 60 | var bufferSize = stride * pixelSize.Height; 61 | 62 | unsafe 63 | { 64 | fixed (byte* ptr = new byte[stride * pixelSize.Height]) 65 | { 66 | var buffer = (nint)ptr; 67 | 68 | bitmap1.CopyPixels(new PixelRect(bitmap1.PixelSize), buffer, bufferSize, stride); 69 | bitmap2.CopyPixels(new PixelRect(bitmap2.PixelSize), buffer + stride1, bufferSize, stride); 70 | 71 | mergedBitmap = new WriteableBitmap(bitmap1.Format.Value, bitmap1.AlphaFormat.Value, buffer, pixelSize, bitmap1.Dpi, stride); 72 | } 73 | } 74 | } 75 | 76 | return mergedBitmap; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /MapControl/Avalonia/LocationAnimator.Avalonia.cs: -------------------------------------------------------------------------------- 1 | namespace MapControl 2 | { 3 | public class LocationAnimator : InterpolatingAnimator 4 | { 5 | public override Location Interpolate(double progress, Location oldValue, Location newValue) 6 | { 7 | return new Location( 8 | (1d - progress) * oldValue.Latitude + progress * newValue.Latitude, 9 | (1d - progress) * oldValue.Longitude + progress * newValue.Longitude); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MapControl/Avalonia/MapContentControl.Avalonia.cs: -------------------------------------------------------------------------------- 1 | namespace MapControl 2 | { 3 | /// 4 | /// ContentControl placed on a MapPanel at a geographic location specified by the Location property. 5 | /// 6 | public class MapContentControl : ContentControl 7 | { 8 | public static readonly StyledProperty AutoCollapseProperty = 9 | MapPanel.AutoCollapseProperty.AddOwner(); 10 | 11 | public static readonly StyledProperty LocationProperty = 12 | MapPanel.LocationProperty.AddOwner(); 13 | 14 | /// 15 | /// Gets/sets MapPanel.AutoCollapse. 16 | /// 17 | public bool AutoCollapse 18 | { 19 | get => GetValue(AutoCollapseProperty); 20 | set => SetValue(AutoCollapseProperty, value); 21 | } 22 | 23 | /// 24 | /// Gets/sets MapPanel.Location. 25 | /// 26 | public Location Location 27 | { 28 | get => GetValue(LocationProperty); 29 | set => SetValue(LocationProperty, value); 30 | } 31 | } 32 | 33 | /// 34 | /// MapContentControl with a Pushpin Style. 35 | /// 36 | public class Pushpin : MapContentControl 37 | { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MapControl/Avalonia/MapControl.Avalonia.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | true 5 | AVALONIA 6 | MapControl 7 | XAML Map Control Library for Avalonia UI 8 | $(GeneratePackage) 9 | XAML.MapControl.Avalonia 10 | $(AssemblyTitle) 11 | A set of Avalonia UI controls for rendering raster maps from different providers like OpenStreetMap and various types of map overlays 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /MapControl/Avalonia/MapImageLayer.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace MapControl 4 | { 5 | public partial class MapImageLayer 6 | { 7 | private void FadeOver() 8 | { 9 | var fadeInAnimation = new Animation 10 | { 11 | FillMode = FillMode.Forward, 12 | Duration = MapBase.ImageFadeDuration, 13 | Children = 14 | { 15 | new KeyFrame 16 | { 17 | KeyTime = MapBase.ImageFadeDuration, 18 | Setters = { new Setter(OpacityProperty, 1d) } 19 | } 20 | } 21 | }; 22 | 23 | _ = fadeInAnimation.RunAsync(Children[1]).ContinueWith( 24 | _ => Children[0].Opacity = 0d, 25 | TaskScheduler.FromCurrentSynchronizationContext()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MapControl/Avalonia/MapItem.Avalonia.cs: -------------------------------------------------------------------------------- 1 | namespace MapControl 2 | { 3 | public partial class MapItem 4 | { 5 | public static readonly StyledProperty AutoCollapseProperty = 6 | MapPanel.AutoCollapseProperty.AddOwner(); 7 | 8 | public static readonly StyledProperty LocationProperty = 9 | MapPanel.LocationProperty.AddOwner(); 10 | 11 | static MapItem() 12 | { 13 | LocationProperty.Changed.AddClassHandler((item, e) => item.UpdateMapTransform()); 14 | } 15 | 16 | protected override void OnPointerPressed(PointerPressedEventArgs e) 17 | { 18 | if (e.Pointer.Type != PointerType.Mouse && 19 | ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl) 20 | { 21 | mapItemsControl.UpdateSelection(this, e); 22 | } 23 | 24 | e.Handled = true; 25 | } 26 | 27 | protected override void OnPointerReleased(PointerReleasedEventArgs e) 28 | { 29 | if (e.Pointer.Type == PointerType.Mouse && 30 | e.InitialPressMouseButton == MouseButton.Left && 31 | ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl) 32 | { 33 | mapItemsControl.UpdateSelection(this, e); 34 | } 35 | 36 | e.Handled = true; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MapControl/Avalonia/MapItemsControl.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls.Presenters; 2 | using Avalonia.Controls.Templates; 3 | 4 | namespace MapControl 5 | { 6 | public partial class MapItemsControl 7 | { 8 | static MapItemsControl() 9 | { 10 | TemplateProperty.OverrideDefaultValue( 11 | new FuncControlTemplate( 12 | (itemsControl, namescope) => new ItemsPresenter { ItemsPanel = itemsControl.ItemsPanel })); 13 | 14 | ItemsPanelProperty.OverrideDefaultValue( 15 | new FuncTemplate(() => new MapPanel())); 16 | } 17 | 18 | public void SelectItemsInGeometry(Geometry geometry) 19 | { 20 | SelectItemsByPosition(geometry.FillContains); 21 | } 22 | 23 | public new MapItem ContainerFromItem(object item) 24 | { 25 | return (MapItem)base.ContainerFromItem(item); 26 | } 27 | 28 | protected override bool NeedsContainerOverride(object item, int index, out object recycleKey) 29 | { 30 | recycleKey = null; 31 | 32 | return item is not MapItem; 33 | } 34 | 35 | protected override Control CreateContainerForItemOverride(object item, int index, object recycleKey) 36 | { 37 | return new MapItem(); 38 | } 39 | 40 | protected override void PrepareContainerForItemOverride(Control container, object item, int index) 41 | { 42 | base.PrepareContainerForItemOverride(container, item, index); 43 | PrepareContainer(container, item); 44 | } 45 | 46 | protected override void ClearContainerForItemOverride(Control container) 47 | { 48 | base.ClearContainerForItemOverride(container); 49 | ClearContainer(container); 50 | } 51 | 52 | internal void UpdateSelection(MapItem mapItem, PointerEventArgs e) 53 | { 54 | if (SelectionMode != SelectionMode.Single && 55 | e.KeyModifiers.HasFlag(KeyModifiers.Shift)) 56 | { 57 | SelectItemsInRange(mapItem); 58 | } 59 | else 60 | { 61 | UpdateSelection(mapItem, true, false, e.KeyModifiers.HasFlag(KeyModifiers.Control)); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /MapControl/Avalonia/MapPanel.Avalonia.cs: -------------------------------------------------------------------------------- 1 | namespace MapControl 2 | { 3 | public partial class MapPanel 4 | { 5 | public static readonly AttachedProperty AutoCollapseProperty = 6 | DependencyPropertyHelper.RegisterAttached("AutoCollapse", typeof(MapPanel)); 7 | 8 | public static readonly AttachedProperty LocationProperty = 9 | DependencyPropertyHelper.RegisterAttached("Location", typeof(MapPanel)); 10 | 11 | public static readonly AttachedProperty BoundingBoxProperty = 12 | DependencyPropertyHelper.RegisterAttached("BoundingBox", typeof(MapPanel)); 13 | 14 | protected Controls ChildElements => Children; 15 | 16 | static MapPanel() 17 | { 18 | AffectsParentArrange(LocationProperty, BoundingBoxProperty); 19 | } 20 | 21 | public static MapBase GetParentMap(Control element) 22 | { 23 | return (MapBase)element.GetValue(ParentMapProperty); 24 | } 25 | 26 | public static void SetRenderTransform(Control element, Transform transform, double originX = 0d, double originY = 0d) 27 | { 28 | element.RenderTransform = transform; 29 | element.RenderTransformOrigin = new RelativePoint(originX, originY, RelativeUnit.Relative); 30 | } 31 | 32 | private static void SetVisible(Control element, bool visible) 33 | { 34 | element.IsVisible = visible; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MapControl/Avalonia/MapPath.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls.Shapes; 2 | 3 | namespace MapControl 4 | { 5 | public partial class MapPath : Shape 6 | { 7 | public static readonly StyledProperty DataProperty = Path.DataProperty.AddOwner(); 8 | 9 | static MapPath() 10 | { 11 | DataProperty.Changed.AddClassHandler((path, e) => path.UpdateData()); 12 | } 13 | 14 | public Geometry Data 15 | { 16 | get => GetValue(DataProperty); 17 | set => SetValue(DataProperty, value); 18 | } 19 | 20 | protected override Geometry CreateDefiningGeometry() => Data; 21 | 22 | private void SetMapTransform(Matrix matrix) 23 | { 24 | if (Data.Transform is MatrixTransform transform) 25 | { 26 | transform.Matrix = matrix; 27 | } 28 | else 29 | { 30 | Data.Transform = new MatrixTransform(matrix); 31 | } 32 | 33 | InvalidateVisual(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MapControl/Avalonia/Tile.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | public partial class Tile 6 | { 7 | private void FadeIn() 8 | { 9 | var fadeInAnimation = new Animation 10 | { 11 | Duration = MapBase.ImageFadeDuration, 12 | Children = 13 | { 14 | new KeyFrame 15 | { 16 | KeyTime = TimeSpan.Zero, 17 | Setters = { new Setter(Visual.OpacityProperty, 0d) } 18 | }, 19 | new KeyFrame 20 | { 21 | KeyTime = MapBase.ImageFadeDuration, 22 | Setters = { new Setter(Visual.OpacityProperty, 1d) } 23 | } 24 | } 25 | }; 26 | 27 | _ = fadeInAnimation.RunAsync(Image); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MapControl/Avalonia/TileImageLoader.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace MapControl 5 | { 6 | public partial class TileImageLoader 7 | { 8 | private static async Task LoadTileImage(Tile tile, Func> loadImageFunc) 9 | { 10 | var image = await loadImageFunc().ConfigureAwait(false); 11 | 12 | _ = Dispatcher.UIThread.InvokeAsync(() => tile.SetImageSource(image)); // no need to await InvokeAsync 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MapControl/Shared/AutoEquirectangularProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows; 4 | #endif 5 | 6 | namespace MapControl 7 | { 8 | /// 9 | /// Auto-Equirectangular Projection - AUTO2:42004. 10 | /// Equidistant cylindrical projection with standard parallel and central meridian set by the Center property. 11 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.90-91. 12 | /// 13 | public class AutoEquirectangularProjection : MapProjection 14 | { 15 | public const string DefaultCrsId = "AUTO2:42004"; 16 | 17 | public AutoEquirectangularProjection() 18 | : this(DefaultCrsId) 19 | { 20 | // XAML needs parameterless constructor 21 | } 22 | 23 | public AutoEquirectangularProjection(string crsId) 24 | { 25 | Type = MapProjectionType.NormalCylindrical; 26 | CrsId = crsId; 27 | } 28 | 29 | public override Point GetRelativeScale(Location location) 30 | { 31 | return new Point( 32 | Math.Cos(Center.Latitude * Math.PI / 180d) / Math.Cos(location.Latitude * Math.PI / 180d), 33 | 1d); 34 | } 35 | 36 | public override Point? LocationToMap(Location location) 37 | { 38 | return new Point( 39 | Wgs84MeterPerDegree * (location.Longitude - Center.Longitude) * Math.Cos(Center.Latitude * Math.PI / 180d), 40 | Wgs84MeterPerDegree * location.Latitude); 41 | } 42 | 43 | public override Location MapToLocation(Point point) 44 | { 45 | return new Location( 46 | point.Y / Wgs84MeterPerDegree, 47 | point.X / (Wgs84MeterPerDegree * Math.Cos(Center.Latitude * Math.PI / 180d)) + Center.Longitude); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MapControl/Shared/AzimuthalEquidistantProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows; 4 | #endif 5 | 6 | namespace MapControl 7 | { 8 | /// 9 | /// Spherical Azimuthal Equidistant Projection - No standard CRS ID. 10 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.195-197. 11 | /// 12 | public class AzimuthalEquidistantProjection : AzimuthalProjection 13 | { 14 | public const string DefaultCrsId = "AUTO2:97003"; // proprietary CRS ID 15 | 16 | public AzimuthalEquidistantProjection() 17 | : this(DefaultCrsId) 18 | { 19 | // XAML needs parameterless constructor 20 | } 21 | 22 | public AzimuthalEquidistantProjection(string crsId) 23 | { 24 | CrsId = crsId; 25 | } 26 | 27 | public override Point? LocationToMap(Location location) 28 | { 29 | if (location.Equals(Center)) 30 | { 31 | return new Point(); 32 | } 33 | 34 | Center.GetAzimuthDistance(location, out double azimuth, out double distance); 35 | 36 | var mapDistance = distance * Wgs84EquatorialRadius; 37 | 38 | return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth)); 39 | } 40 | 41 | public override Location MapToLocation(Point point) 42 | { 43 | if (point.X == 0d && point.Y == 0d) 44 | { 45 | return new Location(Center.Latitude, Center.Longitude); 46 | } 47 | 48 | var azimuth = Math.Atan2(point.X, point.Y); 49 | var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y); 50 | 51 | var distance = mapDistance / Wgs84EquatorialRadius; 52 | 53 | return Center.GetLocation(azimuth, distance); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MapControl/Shared/AzimuthalProjection.cs: -------------------------------------------------------------------------------- 1 | #if WPF 2 | using System.Windows; 3 | #endif 4 | 5 | namespace MapControl 6 | { 7 | /// 8 | /// Base class for azimuthal map projections. 9 | /// 10 | public abstract class AzimuthalProjection : MapProjection 11 | { 12 | protected AzimuthalProjection() 13 | { 14 | Type = MapProjectionType.Azimuthal; 15 | } 16 | 17 | public override Rect? BoundingBoxToMap(BoundingBox boundingBox) 18 | { 19 | Rect? rect = null; 20 | var center = LocationToMap(boundingBox.Center); 21 | 22 | if (center.HasValue) 23 | { 24 | var width = boundingBox.Width * Wgs84MeterPerDegree; 25 | var height = boundingBox.Height * Wgs84MeterPerDegree; 26 | var x = center.Value.X - width / 2d; 27 | var y = center.Value.Y - height / 2d; 28 | 29 | rect = new Rect(x, y, width, height); 30 | } 31 | 32 | return rect; 33 | } 34 | 35 | public override BoundingBox MapToBoundingBox(Rect rect) 36 | { 37 | BoundingBox boundingBox = null; 38 | var rectCenter = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); 39 | var center = MapToLocation(rectCenter); 40 | 41 | if (center != null) 42 | { 43 | boundingBox = new CenteredBoundingBox(center, rect.Width / Wgs84MeterPerDegree, rect.Height / Wgs84MeterPerDegree); 44 | } 45 | 46 | return boundingBox; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MapControl/Shared/BingMapsTileSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | public class BingMapsTileSource : TileSource 6 | { 7 | public override Uri GetUri(int column, int row, int zoomLevel) 8 | { 9 | Uri uri = null; 10 | 11 | if (UriTemplate != null && Subdomains != null && Subdomains.Length > 0 && zoomLevel > 0) 12 | { 13 | var subdomain = Subdomains[(column + row) % Subdomains.Length]; 14 | var quadkey = new char[zoomLevel]; 15 | 16 | for (var z = zoomLevel - 1; z >= 0; z--, column /= 2, row /= 2) 17 | { 18 | quadkey[z] = (char)('0' + 2 * (row % 2) + (column % 2)); 19 | } 20 | 21 | uri = new Uri(UriTemplate 22 | .Replace("{subdomain}", subdomain) 23 | .Replace("{quadkey}", new string(quadkey))); 24 | } 25 | 26 | return uri; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MapControl/Shared/BoundingBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace MapControl 5 | { 6 | /// 7 | /// A geographic bounding box with south and north latitude and west and east longitude values in degrees. 8 | /// 9 | #if UWP || WINUI 10 | [Windows.Foundation.Metadata.CreateFromString(MethodName = "Parse")] 11 | #else 12 | [System.ComponentModel.TypeConverter(typeof(BoundingBoxConverter))] 13 | #endif 14 | public class BoundingBox 15 | { 16 | protected BoundingBox() 17 | { 18 | } 19 | 20 | public BoundingBox(double latitude1, double longitude1, double latitude2, double longitude2) 21 | { 22 | South = Math.Min(Math.Max(Math.Min(latitude1, latitude2), -90d), 90d); 23 | North = Math.Min(Math.Max(Math.Max(latitude1, latitude2), -90d), 90d); 24 | West = Math.Min(longitude1, longitude2); 25 | East = Math.Max(longitude1, longitude2); 26 | } 27 | 28 | public BoundingBox(Location location1, Location location2) 29 | : this(location1.Latitude, location1.Longitude, location2.Latitude, location2.Longitude) 30 | { 31 | } 32 | 33 | public double South { get; } 34 | public double North { get; } 35 | public double West { get; } 36 | public double East { get; } 37 | 38 | public virtual double Width => East - West; 39 | public virtual double Height => North - South; 40 | 41 | public virtual Location Center => new Location((South + North) / 2d, (West + East) / 2d); 42 | 43 | /// 44 | /// Creates a BoundingBox instance from a string containing a comma-separated sequence of four or five floating point numbers. 45 | /// 46 | public static BoundingBox Parse(string boundingBox) 47 | { 48 | string[] values = null; 49 | 50 | if (!string.IsNullOrEmpty(boundingBox)) 51 | { 52 | values = boundingBox.Split(new char[] { ',' }); 53 | } 54 | 55 | if (values == null || values.Length != 4 && values.Length != 5) 56 | { 57 | throw new FormatException($"{nameof(BoundingBox)} string must contain a comma-separated sequence of four or five floating point numbers."); 58 | } 59 | 60 | var rotation = values.Length == 5 61 | ? double.Parse(values[4], NumberStyles.Float, CultureInfo.InvariantCulture) 62 | : 0d; 63 | 64 | // Always return a LatLonBox, i.e. a BoundingBox with optional rotation, as used by GeoImage and GroundOverlay. 65 | // 66 | return new LatLonBox( 67 | double.Parse(values[0], NumberStyles.Float, CultureInfo.InvariantCulture), 68 | double.Parse(values[1], NumberStyles.Float, CultureInfo.InvariantCulture), 69 | double.Parse(values[2], NumberStyles.Float, CultureInfo.InvariantCulture), 70 | double.Parse(values[3], NumberStyles.Float, CultureInfo.InvariantCulture), 71 | rotation); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /MapControl/Shared/BoundingBoxTileSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Threading.Tasks; 4 | #if WPF 5 | using System.Windows.Media; 6 | #elif UWP 7 | using Windows.UI.Xaml.Media; 8 | #elif WINUI 9 | using Microsoft.UI.Xaml.Media; 10 | #endif 11 | 12 | namespace MapControl 13 | { 14 | public class BoundingBoxTileSource : TileSource 15 | { 16 | public override Uri GetUri(int column, int row, int zoomLevel) 17 | { 18 | GetTileBounds(column, row, zoomLevel, out double west, out double south, out double east, out double north); 19 | 20 | return GetUri(west, south, east, north); 21 | } 22 | 23 | public override Task LoadImageAsync(int column, int row, int zoomLevel) 24 | { 25 | GetTileBounds(column, row, zoomLevel, out double west, out double south, out double east, out double north); 26 | 27 | return LoadImageAsync(west, south, east, north); 28 | } 29 | 30 | protected virtual Uri GetUri(double west, double south, double east, double north) 31 | { 32 | Uri uri = null; 33 | 34 | if (UriTemplate != null) 35 | { 36 | var w = west.ToString("F2", CultureInfo.InvariantCulture); 37 | var e = east.ToString("F2", CultureInfo.InvariantCulture); 38 | var s = south.ToString("F2", CultureInfo.InvariantCulture); 39 | var n = north.ToString("F2", CultureInfo.InvariantCulture); 40 | 41 | uri = UriTemplate.Contains("{bbox}") 42 | ? new Uri(UriTemplate.Replace("{bbox}", $"{w},{s},{e},{n}")) 43 | : new Uri(UriTemplate.Replace("{west}", w).Replace("{south}", s).Replace("{east}", e).Replace("{north}", n)); 44 | } 45 | 46 | return uri; 47 | } 48 | 49 | protected virtual Task LoadImageAsync(double west, double south, double east, double north) 50 | { 51 | var uri = GetUri(west, south, east, north); 52 | 53 | return uri != null ? ImageLoader.LoadImageAsync(uri) : Task.FromResult((ImageSource)null); 54 | } 55 | 56 | /// 57 | /// Gets the bounding box in meters of a standard Web Mercator tile, 58 | /// specified by grid column and row indices and zoom level. 59 | /// 60 | public static void GetTileBounds(int column, int row, int zoomLevel, 61 | out double west, out double south, out double east, out double north) 62 | { 63 | var tileSize = 360d / (1 << zoomLevel); // tile size in degrees 64 | 65 | west = MapProjection.Wgs84MeterPerDegree * (column * tileSize - 180d); 66 | east = MapProjection.Wgs84MeterPerDegree * ((column + 1) * tileSize - 180d); 67 | south = MapProjection.Wgs84MeterPerDegree * (180d - (row + 1) * tileSize); 68 | north = MapProjection.Wgs84MeterPerDegree * (180d - row * tileSize); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /MapControl/Shared/CenteredBoundingBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | public class CenteredBoundingBox : BoundingBox 6 | { 7 | public CenteredBoundingBox(Location center, double width, double height) 8 | { 9 | Center = center; 10 | Width = Math.Max(width, 0d); 11 | Height = Math.Max(height, 0d); 12 | } 13 | 14 | public override Location Center { get; } 15 | public override double Width { get; } 16 | public override double Height { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MapControl/Shared/DispatcherTimerHelper.cs: -------------------------------------------------------------------------------- 1 | #if WPF 2 | using System.Windows; 3 | using System.Windows.Threading; 4 | #elif UWP 5 | using Windows.UI.Xaml; 6 | #elif WINUI 7 | global using DispatcherTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer; 8 | using Microsoft.UI.Xaml; 9 | #endif 10 | using System; 11 | 12 | namespace MapControl 13 | { 14 | internal static class DispatcherTimerHelper 15 | { 16 | public static DispatcherTimer CreateTimer(this DependencyObject obj, TimeSpan interval) 17 | { 18 | #if WINUI 19 | var timer = obj.DispatcherQueue.CreateTimer(); 20 | #else 21 | var timer = new DispatcherTimer(); 22 | #endif 23 | timer.Interval = interval; 24 | return timer; 25 | } 26 | 27 | public static void Run(this DispatcherTimer timer, bool restart = false) 28 | { 29 | if (restart) 30 | { 31 | timer.Stop(); 32 | } 33 | #if WINUI 34 | if (!timer.IsRunning) 35 | #else 36 | if (!timer.IsEnabled) 37 | #endif 38 | { 39 | timer.Start(); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MapControl/Shared/EquirectangularProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows; 4 | #endif 5 | 6 | namespace MapControl 7 | { 8 | /// 9 | /// Equirectangular Projection - EPSG:4326. 10 | /// Equidistant cylindrical projection with zero standard parallel and central meridian. 11 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.90-91. 12 | /// 13 | public class EquirectangularProjection : MapProjection 14 | { 15 | public const string DefaultCrsId = "EPSG:4326"; 16 | 17 | public EquirectangularProjection() 18 | : this(DefaultCrsId) 19 | { 20 | // XAML needs parameterless constructor 21 | } 22 | 23 | public EquirectangularProjection(string crsId) 24 | { 25 | Type = MapProjectionType.NormalCylindrical; 26 | CrsId = crsId; 27 | } 28 | 29 | public override Point GetRelativeScale(Location location) 30 | { 31 | return new Point( 32 | 1d / Math.Cos(location.Latitude * Math.PI / 180d), 33 | 1d); 34 | } 35 | 36 | public override Point? LocationToMap(Location location) 37 | { 38 | return new Point( 39 | Wgs84MeterPerDegree * location.Longitude, 40 | Wgs84MeterPerDegree * location.Latitude); 41 | } 42 | 43 | public override Location MapToLocation(Point point) 44 | { 45 | return new Location( 46 | point.Y / Wgs84MeterPerDegree, 47 | point.X / Wgs84MeterPerDegree); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MapControl/Shared/Etrs89UtmProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | /// 6 | /// ETRS89 UTM Projection with zone number. 7 | /// 8 | public class Etrs89UtmProjection : TransverseMercatorProjection 9 | { 10 | public const int FirstZone = 28; 11 | public const int LastZone = 38; 12 | public const int FirstZoneEpsgCode = 25800 + FirstZone; 13 | public const int LastZoneEpsgCode = 25800 + LastZone; 14 | 15 | public int Zone { get; } 16 | 17 | public Etrs89UtmProjection(int zone) 18 | { 19 | if (zone < FirstZone || zone > LastZone) 20 | { 21 | throw new ArgumentException($"Invalid ETRS89 UTM zone {zone}.", nameof(zone)); 22 | } 23 | 24 | Zone = zone; 25 | CrsId = $"EPSG:{25800 + Zone}"; 26 | 27 | // GRS 1980 28 | EquatorialRadius = 6378137d; 29 | Flattening = 1d / 298.257222101; 30 | ScaleFactor = 0.9996; 31 | CentralMeridian = zone * 6d - 183d; 32 | FalseEasting = 5e5; 33 | FalseNorthing = 0d; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MapControl/Shared/FilePath.cs: -------------------------------------------------------------------------------- 1 | namespace MapControl 2 | { 3 | public static class FilePath 4 | { 5 | public static string GetFullPath(string path) 6 | { 7 | #if NET6_0_OR_GREATER 8 | return System.IO.Path.GetFullPath(path, System.AppDomain.CurrentDomain.BaseDirectory); 9 | #else 10 | return System.IO.Path.GetFullPath(path); 11 | #endif 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MapControl/Shared/GnomonicProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows; 4 | #endif 5 | 6 | namespace MapControl 7 | { 8 | /// 9 | /// Spherical Gnomonic Projection - AUTO2:97001. 10 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.165-167. 11 | /// 12 | public class GnomonicProjection : AzimuthalProjection 13 | { 14 | public const string DefaultCrsId = "AUTO2:97001"; // GeoServer non-standard CRS ID 15 | 16 | public GnomonicProjection() 17 | : this(DefaultCrsId) 18 | { 19 | // XAML needs parameterless constructor 20 | } 21 | 22 | public GnomonicProjection(string crsId) 23 | { 24 | CrsId = crsId; 25 | } 26 | 27 | public override Point? LocationToMap(Location location) 28 | { 29 | if (location.Equals(Center)) 30 | { 31 | return new Point(); 32 | } 33 | 34 | Center.GetAzimuthDistance(location, out double azimuth, out double distance); 35 | 36 | var mapDistance = distance < Math.PI / 2d 37 | ? Math.Tan(distance) * Wgs84EquatorialRadius 38 | : double.PositiveInfinity; 39 | 40 | return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth)); 41 | } 42 | 43 | public override Location MapToLocation(Point point) 44 | { 45 | if (point.X == 0d && point.Y == 0d) 46 | { 47 | return new Location(Center.Latitude, Center.Longitude); 48 | } 49 | 50 | var azimuth = Math.Atan2(point.X, point.Y); 51 | var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y); 52 | 53 | var distance = Math.Atan(mapDistance / Wgs84EquatorialRadius); 54 | 55 | return Center.GetLocation(azimuth, distance); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MapControl/Shared/LatLonBox.cs: -------------------------------------------------------------------------------- 1 | namespace MapControl 2 | { 3 | /// 4 | /// A BoundingBox with optional rotation. Used by GeoImage and GroundOverlay. 5 | /// 6 | public class LatLonBox : BoundingBox 7 | { 8 | public LatLonBox(double latitude1, double longitude1, double latitude2, double longitude2, double rotation = 0d) 9 | : base(latitude1, longitude1, latitude2, longitude2) 10 | { 11 | Rotation = rotation; 12 | } 13 | 14 | public LatLonBox(Location location1, Location location2, double rotation = 0d) 15 | : base(location1, location2) 16 | { 17 | Rotation = rotation; 18 | } 19 | 20 | public LatLonBox(BoundingBox boundingBox, double rotation = 0d) 21 | : base(boundingBox.South, boundingBox.West, boundingBox.North, boundingBox.East) 22 | { 23 | Rotation = rotation; 24 | } 25 | 26 | /// 27 | /// Gets a counterclockwise rotation angle in degrees. 28 | /// 29 | public double Rotation { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MapControl/Shared/MapBorderPanel.cs: -------------------------------------------------------------------------------- 1 | #if WPF 2 | using System.Windows; 3 | #elif UWP 4 | using Windows.UI.Xaml; 5 | #elif WINUI 6 | using Microsoft.UI.Xaml; 7 | #endif 8 | 9 | namespace MapControl 10 | { 11 | /// 12 | /// A MapPanel that adjusts the ViewPosition property of its child elements so that 13 | /// elements that would be outside the current viewport are arranged on a border area. 14 | /// Such elements are arranged at a distance of BorderWidth/2 from the edges of the 15 | /// MapBorderPanel in direction of their original azimuth from the map center. 16 | /// 17 | public class MapBorderPanel : MapPanel 18 | { 19 | public static readonly DependencyProperty BorderWidthProperty = 20 | DependencyPropertyHelper.Register(nameof(BorderWidth)); 21 | 22 | public static readonly DependencyProperty OnBorderProperty = 23 | DependencyPropertyHelper.RegisterAttached("OnBorder", typeof(MapBorderPanel)); 24 | 25 | public double BorderWidth 26 | { 27 | get => (double)GetValue(BorderWidthProperty); 28 | set => SetValue(BorderWidthProperty, value); 29 | } 30 | 31 | public static bool GetOnBorder(FrameworkElement element) 32 | { 33 | return (bool)element.GetValue(OnBorderProperty); 34 | } 35 | 36 | protected override void SetViewPosition(FrameworkElement element, ref Point? position) 37 | { 38 | var onBorder = false; 39 | var w = ParentMap.ActualWidth; 40 | var h = ParentMap.ActualHeight; 41 | var minX = BorderWidth / 2d; 42 | var minY = BorderWidth / 2d; 43 | var maxX = w - BorderWidth / 2d; 44 | var maxY = h - BorderWidth / 2d; 45 | 46 | if (position.HasValue && 47 | (position.Value.X < minX || position.Value.X > maxX || 48 | position.Value.Y < minY || position.Value.Y > maxY)) 49 | { 50 | var dx = position.Value.X - w / 2d; 51 | var dy = position.Value.Y - h / 2d; 52 | var cx = (maxX - minX) / 2d; 53 | var cy = (maxY - minY) / 2d; 54 | double x, y; 55 | 56 | if (dx < 0d) 57 | { 58 | x = minX; 59 | y = minY + cy - cx * dy / dx; 60 | } 61 | else 62 | { 63 | x = maxX; 64 | y = minY + cy + cx * dy / dx; 65 | } 66 | 67 | if (y < minY || y > maxY) 68 | { 69 | if (dy < 0d) 70 | { 71 | x = minX + cx - cy * dx / dy; 72 | y = minY; 73 | } 74 | else 75 | { 76 | x = minX + cx + cy * dx / dy; 77 | y = maxY; 78 | } 79 | } 80 | 81 | position = new Point(x, y); 82 | onBorder = true; 83 | } 84 | 85 | element.SetValue(OnBorderProperty, onBorder); 86 | 87 | base.SetViewPosition(element, ref position); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /MapControl/Shared/MapItem.cs: -------------------------------------------------------------------------------- 1 | #if WPF 2 | using System.Windows.Controls; 3 | using System.Windows.Media; 4 | #elif UWP 5 | using Windows.UI.Xaml.Controls; 6 | using Windows.UI.Xaml.Media; 7 | #elif WINUI 8 | using Microsoft.UI.Xaml.Controls; 9 | using Microsoft.UI.Xaml.Media; 10 | #endif 11 | 12 | using System; 13 | 14 | namespace MapControl 15 | { 16 | /// 17 | /// Container class for an item in a MapItemsControl. 18 | /// 19 | public partial class MapItem : ListBoxItem, IMapElement 20 | { 21 | private MapBase parentMap; 22 | private MatrixTransform mapTransform; 23 | 24 | /// 25 | /// Gets/sets MapPanel.AutoCollapse. 26 | /// 27 | public bool AutoCollapse 28 | { 29 | get => (bool)GetValue(AutoCollapseProperty); 30 | set => SetValue(AutoCollapseProperty, value); 31 | } 32 | 33 | /// 34 | /// Gets/sets MapPanel.Location. 35 | /// 36 | public Location Location 37 | { 38 | get => (Location)GetValue(LocationProperty); 39 | set => SetValue(LocationProperty, value); 40 | } 41 | 42 | /// 43 | /// Implements IMapElement.ParentMap. 44 | /// 45 | public MapBase ParentMap 46 | { 47 | get => parentMap; 48 | set 49 | { 50 | if (parentMap != null) 51 | { 52 | parentMap.ViewportChanged -= OnViewportChanged; 53 | } 54 | 55 | parentMap = value; 56 | 57 | if (parentMap != null && mapTransform != null) 58 | { 59 | // Attach ViewportChanged handler only if MapTransform is actually used. 60 | // 61 | parentMap.ViewportChanged += OnViewportChanged; 62 | 63 | UpdateMapTransform(); 64 | } 65 | } 66 | } 67 | 68 | /// 69 | /// Gets a Transform for scaling and rotating geometries 70 | /// in map coordinates (meters) to view coordinates (pixels). 71 | /// 72 | public Transform MapTransform 73 | { 74 | get 75 | { 76 | if (mapTransform == null) 77 | { 78 | mapTransform = new MatrixTransform(); 79 | 80 | if (parentMap != null) 81 | { 82 | parentMap.ViewportChanged += OnViewportChanged; 83 | 84 | UpdateMapTransform(); 85 | } 86 | } 87 | 88 | return mapTransform; 89 | } 90 | } 91 | 92 | private void OnViewportChanged(object sender, ViewportChangedEventArgs e) 93 | { 94 | UpdateMapTransform(); 95 | } 96 | 97 | private void UpdateMapTransform() 98 | { 99 | if (mapTransform != null && parentMap != null && Location != null) 100 | { 101 | mapTransform.Matrix = parentMap.GetMapTransform(Location); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /MapControl/Shared/MapMultiPolygon.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | #if WPF 3 | using System.Windows; 4 | #elif UWP 5 | using Windows.UI.Xaml; 6 | #elif WINUI 7 | using Microsoft.UI.Xaml; 8 | #endif 9 | 10 | namespace MapControl 11 | { 12 | /// 13 | /// A multi-polygon defined by a collection of collections of Locations. 14 | /// Allows to draw filled polygons with holes. 15 | /// 16 | /// A PolygonCollection (with ObservableCollection of Location elements) may be used 17 | /// for the Polygons property if collection changes of the property itself and its 18 | /// elements are both supposed to trigger UI updates. 19 | /// 20 | public class MapMultiPolygon : MapPolypoint 21 | { 22 | public static readonly DependencyProperty PolygonsProperty = 23 | DependencyPropertyHelper.Register>>(nameof(Polygons), null, 24 | (polygon, oldValue, newValue) => polygon.DataCollectionPropertyChanged(oldValue, newValue)); 25 | 26 | /// 27 | /// Gets or sets the Locations that define the multi-polygon points. 28 | /// 29 | public IEnumerable> Polygons 30 | { 31 | get => (IEnumerable>)GetValue(PolygonsProperty); 32 | set => SetValue(PolygonsProperty, value); 33 | } 34 | 35 | protected override void UpdateData() 36 | { 37 | UpdateData(Polygons); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MapControl/Shared/MapPolygon.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | #if WPF 3 | using System.Windows; 4 | #elif UWP 5 | using Windows.UI.Xaml; 6 | #elif WINUI 7 | using Microsoft.UI.Xaml; 8 | #endif 9 | 10 | namespace MapControl 11 | { 12 | /// 13 | /// A polygon defined by a collection of Locations. 14 | /// 15 | public class MapPolygon : MapPolypoint 16 | { 17 | public static readonly DependencyProperty LocationsProperty = 18 | DependencyPropertyHelper.Register>(nameof(Locations), null, 19 | (polygon, oldValue, newValue) => polygon.DataCollectionPropertyChanged(oldValue, newValue)); 20 | 21 | /// 22 | /// Gets or sets the Locations that define the polygon points. 23 | /// 24 | #if WPF 25 | [System.ComponentModel.TypeConverter(typeof(LocationCollectionConverter))] 26 | #endif 27 | public IEnumerable Locations 28 | { 29 | get => (IEnumerable)GetValue(LocationsProperty); 30 | set => SetValue(LocationsProperty, value); 31 | } 32 | 33 | protected override void UpdateData() 34 | { 35 | UpdateData(Locations, true); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MapControl/Shared/MapPolyline.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | #if WPF 3 | using System.Windows; 4 | #elif UWP 5 | using Windows.UI.Xaml; 6 | #elif WINUI 7 | using Microsoft.UI.Xaml; 8 | #endif 9 | 10 | namespace MapControl 11 | { 12 | /// 13 | /// A polyline defined by a collection of Locations. 14 | /// 15 | public class MapPolyline : MapPolypoint 16 | { 17 | public static readonly DependencyProperty LocationsProperty = 18 | DependencyPropertyHelper.Register>(nameof(Locations), null, 19 | (polyline, oldValue, newValue) => polyline.DataCollectionPropertyChanged(oldValue, newValue)); 20 | 21 | /// 22 | /// Gets or sets the Locations that define the polyline points. 23 | /// 24 | #if WPF 25 | [System.ComponentModel.TypeConverter(typeof(LocationCollectionConverter))] 26 | #endif 27 | public IEnumerable Locations 28 | { 29 | get => (IEnumerable)GetValue(LocationsProperty); 30 | set => SetValue(LocationsProperty, value); 31 | } 32 | 33 | protected override void UpdateData() 34 | { 35 | UpdateData(Locations, false); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MapControl/Shared/Nad27UtmProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | /// 6 | /// NAD27 UTM Projection with zone number. 7 | /// 8 | public class Nad27UtmProjection : TransverseMercatorProjection 9 | { 10 | public const int FirstZone = 1; 11 | public const int LastZone = 22; 12 | public const int FirstZoneEpsgCode = 26700 + FirstZone; 13 | public const int LastZoneEpsgCode = 26700 + LastZone; 14 | 15 | public int Zone { get; } 16 | 17 | public Nad27UtmProjection(int zone) 18 | { 19 | if (zone < FirstZone || zone > LastZone) 20 | { 21 | throw new ArgumentException($"Invalid NAD27 UTM zone {zone}.", nameof(zone)); 22 | } 23 | 24 | Zone = zone; 25 | CrsId = $"EPSG:{26700 + Zone}"; 26 | 27 | // Clarke 1866 28 | EquatorialRadius = 6378206.4; 29 | Flattening = 1d / 294.978698213898; 30 | ScaleFactor = 0.9996; 31 | CentralMeridian = zone * 6d - 183d; 32 | FalseEasting = 5e5; 33 | FalseNorthing = 0d; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MapControl/Shared/Nad83UtmProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | /// 6 | /// NAD83 UTM Projection with zone number. 7 | /// 8 | public class Nad83UtmProjection : TransverseMercatorProjection 9 | { 10 | public const int FirstZone = 1; 11 | public const int LastZone = 23; 12 | public const int FirstZoneEpsgCode = 26900 + FirstZone; 13 | public const int LastZoneEpsgCode = 26900 + LastZone; 14 | 15 | public int Zone { get; } 16 | 17 | public Nad83UtmProjection(int zone) 18 | { 19 | if (zone < FirstZone || zone > LastZone) 20 | { 21 | throw new ArgumentException($"Invalid NAD83 UTM zone {zone}.", nameof(zone)); 22 | } 23 | 24 | Zone = zone; 25 | CrsId = $"EPSG:{26900 + Zone}"; 26 | 27 | // GRS 1980 28 | EquatorialRadius = 6378137d; 29 | Flattening = 1d / 298.257222101; 30 | ScaleFactor = 0.9996; 31 | CentralMeridian = zone * 6d - 183d; 32 | FalseEasting = 5e5; 33 | FalseNorthing = 0d; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MapControl/Shared/OrthographicProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows; 4 | #endif 5 | 6 | namespace MapControl 7 | { 8 | /// 9 | /// Spherical Orthographic Projection - AUTO2:42003. 10 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.148-150. 11 | /// 12 | public class OrthographicProjection : AzimuthalProjection 13 | { 14 | public const string DefaultCrsId = "AUTO2:42003"; 15 | 16 | public OrthographicProjection() 17 | : this(DefaultCrsId) 18 | { 19 | // XAML needs parameterless constructor 20 | } 21 | 22 | public OrthographicProjection(string crsId) 23 | { 24 | CrsId = crsId; 25 | } 26 | 27 | public override Point? LocationToMap(Location location) 28 | { 29 | if (location.Equals(Center)) 30 | { 31 | return new Point(); 32 | } 33 | 34 | var lat0 = Center.Latitude * Math.PI / 180d; 35 | var lat = location.Latitude * Math.PI / 180d; 36 | var dLon = (location.Longitude - Center.Longitude) * Math.PI / 180d; 37 | 38 | if (Math.Abs(lat - lat0) > Math.PI / 2d || Math.Abs(dLon) > Math.PI / 2d) 39 | { 40 | return null; 41 | } 42 | 43 | return new Point( 44 | Wgs84EquatorialRadius * Math.Cos(lat) * Math.Sin(dLon), 45 | Wgs84EquatorialRadius * (Math.Cos(lat0) * Math.Sin(lat) - Math.Sin(lat0) * Math.Cos(lat) * Math.Cos(dLon))); 46 | } 47 | 48 | public override Location MapToLocation(Point point) 49 | { 50 | if (point.X == 0d && point.Y == 0d) 51 | { 52 | return new Location(Center.Latitude, Center.Longitude); 53 | } 54 | 55 | var x = point.X / Wgs84EquatorialRadius; 56 | var y = point.Y / Wgs84EquatorialRadius; 57 | var r2 = x * x + y * y; 58 | 59 | if (r2 > 1d) 60 | { 61 | return null; 62 | } 63 | 64 | var r = Math.Sqrt(r2); 65 | var sinC = r; 66 | var cosC = Math.Sqrt(1 - r2); 67 | 68 | var lat0 = Center.Latitude * Math.PI / 180d; 69 | var cosLat0 = Math.Cos(lat0); 70 | var sinLat0 = Math.Sin(lat0); 71 | 72 | return new Location( 73 | 180d / Math.PI * Math.Asin(cosC * sinLat0 + y * sinC * cosLat0 / r), 74 | 180d / Math.PI * Math.Atan2(x * sinC, r * cosC * cosLat0 - y * sinC * sinLat0) + Center.Longitude); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /MapControl/Shared/PolygonCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Collections.Specialized; 4 | using System.Linq; 5 | 6 | namespace MapControl 7 | { 8 | /// 9 | /// An ObservableCollection of IEnumerable of Location. PolygonCollection adds a CollectionChanged 10 | /// listener to each element that implements INotifyCollectionChanged and, when such an element changes, 11 | /// fires its own CollectionChanged event with NotifyCollectionChangedAction.Replace for that element. 12 | /// 13 | public class PolygonCollection : ObservableCollection> 14 | { 15 | private void PolygonChanged(object sender, NotifyCollectionChangedEventArgs e) 16 | { 17 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender)); 18 | } 19 | 20 | protected override void InsertItem(int index, IEnumerable polygon) 21 | { 22 | if (polygon is INotifyCollectionChanged addedPolygon) 23 | { 24 | addedPolygon.CollectionChanged += PolygonChanged; 25 | } 26 | 27 | base.InsertItem(index, polygon); 28 | } 29 | 30 | protected override void SetItem(int index, IEnumerable polygon) 31 | { 32 | if (this[index] is INotifyCollectionChanged removedPolygon) 33 | { 34 | removedPolygon.CollectionChanged -= PolygonChanged; 35 | } 36 | 37 | if (polygon is INotifyCollectionChanged addedPolygon) 38 | { 39 | addedPolygon.CollectionChanged += PolygonChanged; 40 | } 41 | 42 | base.SetItem(index, polygon); 43 | } 44 | 45 | protected override void RemoveItem(int index) 46 | { 47 | if (this[index] is INotifyCollectionChanged removedPolygon) 48 | { 49 | removedPolygon.CollectionChanged -= PolygonChanged; 50 | } 51 | 52 | base.RemoveItem(index); 53 | } 54 | 55 | protected override void ClearItems() 56 | { 57 | foreach (var polygon in this.OfType()) 58 | { 59 | polygon.CollectionChanged -= PolygonChanged; 60 | } 61 | 62 | base.ClearItems(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /MapControl/Shared/StereographicProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows; 4 | #endif 5 | 6 | namespace MapControl 7 | { 8 | /// 9 | /// Spherical Stereographic Projection - AUTO2:97002. 10 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.157-160. 11 | /// 12 | public class StereographicProjection : AzimuthalProjection 13 | { 14 | public const string DefaultCrsId = "AUTO2:97002"; // GeoServer non-standard CRS ID 15 | 16 | public StereographicProjection() 17 | : this(DefaultCrsId) 18 | { 19 | // XAML needs parameterless constructor 20 | } 21 | 22 | public StereographicProjection(string crsId) 23 | { 24 | CrsId = crsId; 25 | } 26 | 27 | public override Point? LocationToMap(Location location) 28 | { 29 | if (location.Equals(Center)) 30 | { 31 | return new Point(); 32 | } 33 | 34 | Center.GetAzimuthDistance(location, out double azimuth, out double distance); 35 | 36 | var mapDistance = Math.Tan(distance / 2d) * 2d * Wgs84EquatorialRadius; 37 | 38 | return new Point(mapDistance * Math.Sin(azimuth), mapDistance * Math.Cos(azimuth)); 39 | } 40 | 41 | public override Location MapToLocation(Point point) 42 | { 43 | if (point.X == 0d && point.Y == 0d) 44 | { 45 | return new Location(Center.Latitude, Center.Longitude); 46 | } 47 | 48 | var azimuth = Math.Atan2(point.X, point.Y); 49 | var mapDistance = Math.Sqrt(point.X * point.X + point.Y * point.Y); 50 | 51 | var distance = 2d * Math.Atan(mapDistance / (2d * Wgs84EquatorialRadius)); 52 | 53 | return Center.GetLocation(azimuth, distance); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MapControl/Shared/Tile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows.Controls; 4 | using System.Windows.Media; 5 | #elif UWP 6 | using Windows.UI.Xaml.Controls; 7 | using Windows.UI.Xaml.Media; 8 | #elif WINUI 9 | using Microsoft.UI.Xaml.Controls; 10 | using Microsoft.UI.Xaml.Media; 11 | #endif 12 | 13 | namespace MapControl 14 | { 15 | public partial class Tile 16 | { 17 | public Tile(int zoomLevel, int x, int y, int columnCount) 18 | { 19 | ZoomLevel = zoomLevel; 20 | X = x; 21 | Y = y; 22 | Column = ((x % columnCount) + columnCount) % columnCount; 23 | } 24 | 25 | public int ZoomLevel { get; } 26 | public int X { get; } 27 | public int Y { get; } 28 | public int Column { get; } 29 | public int Row => Y; 30 | 31 | public Image Image { get; } = new Image { Stretch = Stretch.Fill }; 32 | 33 | public bool IsPending { get; set; } = true; 34 | 35 | public void SetImageSource(ImageSource image) 36 | { 37 | IsPending = false; 38 | Image.Source = image; 39 | 40 | if (image != null && MapBase.ImageFadeDuration > TimeSpan.Zero) 41 | { 42 | FadeIn(); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MapControl/Shared/TileCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace MapControl 5 | { 6 | public class TileCollection : List 7 | { 8 | /// 9 | /// Get a matching Tile from a TileCollection or create a new one. 10 | /// 11 | public Tile GetTile(int zoomLevel, int x, int y, int columnCount) 12 | { 13 | var tile = this.FirstOrDefault(t => t.ZoomLevel == zoomLevel && t.X == x && t.Y == y); 14 | 15 | if (tile == null) 16 | { 17 | tile = new Tile(zoomLevel, x, y, columnCount); 18 | 19 | var equivalentTile = this.FirstOrDefault( 20 | t => t.Image.Source != null && t.ZoomLevel == tile.ZoomLevel && t.Column == tile.Column && t.Row == tile.Row); 21 | 22 | if (equivalentTile != null) 23 | { 24 | tile.IsPending = false; 25 | tile.Image.Source = equivalentTile.Image.Source; // no opacity animation 26 | } 27 | } 28 | 29 | return tile; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MapControl/Shared/TileMatrix.cs: -------------------------------------------------------------------------------- 1 | namespace MapControl 2 | { 3 | public class TileMatrix 4 | { 5 | public TileMatrix(int zoomLevel, int xMin, int yMin, int xMax, int yMax) 6 | { 7 | ZoomLevel = zoomLevel; 8 | XMin = xMin; 9 | YMin = yMin; 10 | XMax = xMax; 11 | YMax = yMax; 12 | } 13 | 14 | public int ZoomLevel { get; } 15 | public int XMin { get; } 16 | public int YMin { get; } 17 | public int XMax { get; } 18 | public int YMax { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MapControl/Shared/TypeConverters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | 5 | namespace MapControl 6 | { 7 | public class LocationConverter : TypeConverter 8 | { 9 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 10 | { 11 | return sourceType == typeof(string); 12 | } 13 | 14 | public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 15 | { 16 | return Location.Parse((string)value); 17 | } 18 | } 19 | 20 | public class LocationCollectionConverter : TypeConverter 21 | { 22 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 23 | { 24 | return sourceType == typeof(string); 25 | } 26 | 27 | public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 28 | { 29 | return LocationCollection.Parse((string)value); 30 | } 31 | } 32 | 33 | public class BoundingBoxConverter : TypeConverter 34 | { 35 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 36 | { 37 | return sourceType == typeof(string); 38 | } 39 | 40 | public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 41 | { 42 | return BoundingBox.Parse((string)value); 43 | } 44 | } 45 | 46 | public class TileSourceConverter : TypeConverter 47 | { 48 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 49 | { 50 | return sourceType == typeof(string); 51 | } 52 | 53 | public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 54 | { 55 | return TileSource.Parse((string)value); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MapControl/Shared/ViewportChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | public class ViewportChangedEventArgs : EventArgs 6 | { 7 | public ViewportChangedEventArgs(bool projectionChanged = false, bool transformCenterChanged = false) 8 | { 9 | ProjectionChanged = projectionChanged; 10 | TransformCenterChanged = transformCenterChanged; 11 | } 12 | 13 | /// 14 | /// Indicates that the map projection has changed. Used to control when 15 | /// a MapTileLayer or a MapImageLayer should be updated immediately, 16 | /// or MapPath Data in projected map coordinates should be recalculated. 17 | /// 18 | public bool ProjectionChanged { get; } 19 | 20 | /// 21 | /// Indicates that the view transform center has moved across 180° longitude. 22 | /// Used to control when a MapTileLayer should be updated immediately. 23 | /// 24 | public bool TransformCenterChanged { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MapControl/Shared/WebMercatorProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows; 4 | #endif 5 | 6 | namespace MapControl 7 | { 8 | /// 9 | /// Spherical Mercator Projection - EPSG:3857. 10 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.41-44. 11 | /// 12 | public class WebMercatorProjection : MapProjection 13 | { 14 | public const string DefaultCrsId = "EPSG:3857"; 15 | 16 | public WebMercatorProjection() 17 | : this(DefaultCrsId) 18 | { 19 | // XAML needs parameterless constructor 20 | } 21 | 22 | public WebMercatorProjection(string crsId) 23 | { 24 | Type = MapProjectionType.WebMercator; 25 | CrsId = crsId; 26 | } 27 | 28 | public override Point GetRelativeScale(Location location) 29 | { 30 | var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3) 31 | 32 | return new Point(k, k); 33 | } 34 | 35 | public override Point? LocationToMap(Location location) 36 | { 37 | return new Point( 38 | Wgs84MeterPerDegree * location.Longitude, 39 | Wgs84MeterPerDegree * LatitudeToY(location.Latitude)); 40 | } 41 | 42 | public override Location MapToLocation(Point point) 43 | { 44 | return new Location( 45 | YToLatitude(point.Y / Wgs84MeterPerDegree), 46 | point.X / Wgs84MeterPerDegree); 47 | } 48 | 49 | public static double LatitudeToY(double latitude) 50 | { 51 | if (latitude <= -90d) 52 | { 53 | return double.NegativeInfinity; 54 | } 55 | 56 | if (latitude >= 90d) 57 | { 58 | return double.PositiveInfinity; 59 | } 60 | 61 | return Math.Log(Math.Tan((latitude + 90d) * Math.PI / 360d)) * 180d / Math.PI; 62 | } 63 | 64 | public static double YToLatitude(double y) 65 | { 66 | return 90d - Math.Atan(Math.Exp(-y * Math.PI / 180d)) * 360d / Math.PI; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /MapControl/Shared/Wgs84UtmProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | /// 6 | /// WGS84 UTM Projection with zone number and north/south flag. 7 | /// 8 | public class Wgs84UtmProjection : TransverseMercatorProjection 9 | { 10 | public const int FirstZone = 1; 11 | public const int LastZone = 60; 12 | public const int FirstZoneNorthEpsgCode = 32600 + FirstZone; 13 | public const int LastZoneNorthEpsgCode = 32600 + LastZone; 14 | public const int FirstZoneSouthEpsgCode = 32700 + FirstZone; 15 | public const int LastZoneSouthEpsgCode = 32700 + LastZone; 16 | 17 | public int Zone { get; private set; } 18 | public bool IsNorth { get; private set; } 19 | 20 | public Wgs84UtmProjection(int zone, bool north) 21 | { 22 | SetZone(zone, north); 23 | 24 | EquatorialRadius = Wgs84EquatorialRadius; 25 | Flattening = Wgs84Flattening; 26 | ScaleFactor = 0.9996; 27 | FalseEasting = 5e5; 28 | } 29 | 30 | protected void SetZone(int zone, bool north) 31 | { 32 | if (zone < FirstZone || zone > LastZone) 33 | { 34 | throw new ArgumentException($"Invalid WGS84 UTM zone {zone}.", nameof(zone)); 35 | } 36 | 37 | Zone = zone; 38 | IsNorth = north; 39 | CrsId = $"EPSG:{(north ? 32600 : 32700) + zone}"; 40 | CentralMeridian = zone * 6d - 183d; 41 | FalseNorthing = north ? 0d : 1e7; 42 | } 43 | } 44 | 45 | /// 46 | /// WGS84 UTM Projection with automatic zone selection from projection center. 47 | /// 48 | public class Wgs84AutoUtmProjection : Wgs84UtmProjection 49 | { 50 | public const string DefaultCrsId = "AUTO2:42001"; 51 | 52 | private readonly string autoCrsId; 53 | 54 | public Wgs84AutoUtmProjection(string crsId = DefaultCrsId) 55 | : base(31, true) 56 | { 57 | autoCrsId = crsId; 58 | 59 | if (!string.IsNullOrEmpty(autoCrsId)) 60 | { 61 | CrsId = autoCrsId; 62 | } 63 | } 64 | 65 | public override Location Center 66 | { 67 | get => base.Center; 68 | protected internal set 69 | { 70 | if (!Equals(base.Center, value)) 71 | { 72 | base.Center = value; 73 | 74 | var lon = Location.NormalizeLongitude(value.Longitude); 75 | var zone = (int)Math.Floor(lon / 6d) + 31; 76 | var north = value.Latitude >= 0d; 77 | 78 | if (Zone != zone || IsNorth != north) 79 | { 80 | SetZone(zone, north); 81 | 82 | if (!string.IsNullOrEmpty(autoCrsId)) 83 | { 84 | CrsId = autoCrsId; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /MapControl/Shared/WmtsTileMatrix.cs: -------------------------------------------------------------------------------- 1 | #if WPF 2 | using System.Windows; 3 | #endif 4 | 5 | namespace MapControl 6 | { 7 | public class WmtsTileMatrix 8 | { 9 | // See 07-057r7_Web_Map_Tile_Service_Standard.pdf, section 6.1.a, page 8: 10 | // "standardized rendering pixel size" is 0.28 mm 11 | 12 | public WmtsTileMatrix(string identifier, double scaleDenominator, Point topLeft, 13 | int tileWidth, int tileHeight, int matrixWidth, int matrixHeight) 14 | { 15 | Identifier = identifier; 16 | Scale = 1 / (scaleDenominator * 0.00028); // 0.28 mm 17 | TopLeft = topLeft; 18 | TileWidth = tileWidth; 19 | TileHeight = tileHeight; 20 | MatrixWidth = matrixWidth; 21 | MatrixHeight = matrixHeight; 22 | } 23 | 24 | public string Identifier { get; } 25 | public double Scale { get; } 26 | public Point TopLeft { get; } 27 | public int TileWidth { get; } 28 | public int TileHeight { get; } 29 | public int MatrixWidth { get; } 30 | public int MatrixHeight { get; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MapControl/Shared/WmtsTileMatrixSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace MapControl 6 | { 7 | public class WmtsTileMatrixSet 8 | { 9 | public WmtsTileMatrixSet(string identifier, string supportedCrs, IEnumerable tileMatrixes) 10 | { 11 | if (string.IsNullOrEmpty(identifier)) 12 | { 13 | throw new ArgumentException($"The {nameof(identifier)} argument must not be null or empty.", nameof(identifier)); 14 | } 15 | 16 | if (string.IsNullOrEmpty(supportedCrs)) 17 | { 18 | throw new ArgumentException($"The {nameof(supportedCrs)} argument must not be null or empty.", nameof(supportedCrs)); 19 | } 20 | 21 | if (tileMatrixes == null || !tileMatrixes.Any()) 22 | { 23 | throw new ArgumentException($"The {nameof(tileMatrixes)} argument must not be null or an empty collection.", nameof(tileMatrixes)); 24 | } 25 | 26 | Identifier = identifier; 27 | SupportedCrs = supportedCrs; 28 | TileMatrixes = tileMatrixes.OrderBy(m => m.Scale).ToList(); 29 | } 30 | 31 | public string Identifier { get; } 32 | public string SupportedCrs { get; } 33 | public IList TileMatrixes { get; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MapControl/Shared/WmtsTileSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | public class WmtsTileSource : TileSource 6 | { 7 | public WmtsTileMatrixSet TileMatrixSet { get; set; } 8 | 9 | public override Uri GetUri(int column, int row, int zoomLevel) 10 | { 11 | Uri uri = null; 12 | 13 | if (UriTemplate != null && 14 | TileMatrixSet != null && TileMatrixSet.TileMatrixes.Count > zoomLevel && 15 | column >= 0 && row >= 0 && zoomLevel >= 0) 16 | { 17 | uri = new Uri(UriTemplate 18 | .Replace("{TileMatrixSet}", TileMatrixSet.Identifier) 19 | .Replace("{TileMatrix}", TileMatrixSet.TileMatrixes[zoomLevel].Identifier) 20 | .Replace("{TileCol}", column.ToString()) 21 | .Replace("{TileRow}", row.ToString())); 22 | } 23 | 24 | return uri; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MapControl/Shared/WorldMercatorProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows; 4 | #endif 5 | 6 | namespace MapControl 7 | { 8 | /// 9 | /// Elliptical Mercator Projection - EPSG:3395. 10 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.44-45. 11 | /// 12 | public class WorldMercatorProjection : MapProjection 13 | { 14 | public const string DefaultCrsId = "EPSG:3395"; 15 | 16 | public WorldMercatorProjection() 17 | : this(DefaultCrsId) 18 | { 19 | // XAML needs parameterless constructor 20 | } 21 | 22 | public WorldMercatorProjection(string crsId) 23 | { 24 | Type = MapProjectionType.NormalCylindrical; 25 | CrsId = crsId; 26 | } 27 | 28 | public override Point GetRelativeScale(Location location) 29 | { 30 | var lat = location.Latitude * Math.PI / 180d; 31 | var eSinLat = Wgs84Eccentricity * Math.Sin(lat); 32 | var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8) 33 | 34 | return new Point(k, k); 35 | } 36 | 37 | public override Point? LocationToMap(Location location) 38 | { 39 | return new Point( 40 | Wgs84MeterPerDegree * location.Longitude, 41 | Wgs84MeterPerDegree * LatitudeToY(location.Latitude)); 42 | } 43 | 44 | public override Location MapToLocation(Point point) 45 | { 46 | return new Location( 47 | YToLatitude(point.Y / Wgs84MeterPerDegree), 48 | point.X / Wgs84MeterPerDegree); 49 | } 50 | 51 | public static double LatitudeToY(double latitude) 52 | { 53 | if (latitude <= -90d) 54 | { 55 | return double.NegativeInfinity; 56 | } 57 | 58 | if (latitude >= 90d) 59 | { 60 | return double.PositiveInfinity; 61 | } 62 | 63 | var lat = latitude * Math.PI / 180d; 64 | var eSinLat = Wgs84Eccentricity * Math.Sin(lat); 65 | var f = Math.Pow((1d - eSinLat) / (1d + eSinLat), Wgs84Eccentricity / 2d); 66 | 67 | return Math.Log(Math.Tan(lat / 2d + Math.PI / 4d) * f) * 180d / Math.PI; // p.44 (7-7) 68 | } 69 | 70 | public static double YToLatitude(double y) 71 | { 72 | var t = Math.Exp(-y * Math.PI / 180d); // p.44 (7-10) 73 | 74 | return ApproximateLatitude(Wgs84Eccentricity, t) * 180d / Math.PI; 75 | } 76 | 77 | internal static double ApproximateLatitude(double e, double t) 78 | { 79 | var e_2 = e * e; 80 | var e_4 = e_2 * e_2; 81 | var e_6 = e_2 * e_4; 82 | var e_8 = e_2 * e_6; 83 | 84 | var lat = Math.PI / 2d - 2d * Math.Atan(t); // p.45 (7-13) 85 | 86 | return lat 87 | + (e_2 / 2d + 5d * e_4 / 24d + e_6 / 12d + 13d * e_8 / 360d) * Math.Sin(2d * lat) 88 | + (7d * e_4 / 48d + 29d * e_6 / 240d + 811d * e_8 / 11520d) * Math.Sin(4d * lat) 89 | + (7d * e_6 / 120d + 81d * e_8 / 1120d) * Math.Sin(6d * lat) 90 | + 4279d * e_8 / 161280d * Math.Sin(8d * lat); // p.45 (3-5) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /MapControl/UWP/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("XAML Map Control Library for UWP")] 5 | [assembly: AssemblyProduct("XAML Map Control")] 6 | [assembly: AssemblyCompany("Clemens Fischer")] 7 | [assembly: AssemblyCopyright("Copyright © 2024 Clemens Fischer")] 8 | [assembly: AssemblyTrademark("")] 9 | [assembly: AssemblyVersion("13.0.0")] 10 | [assembly: AssemblyFileVersion("13.0.0")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCulture("")] 13 | [assembly: ComVisible(false)] 14 | -------------------------------------------------------------------------------- /MapControl/UWP/Properties/MapControl.UWP.rd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MapControl/UWP/TileImageLoader.UWP.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Windows.UI.Core; 4 | using Windows.UI.Xaml.Media; 5 | 6 | namespace MapControl 7 | { 8 | public partial class TileImageLoader 9 | { 10 | private static async Task LoadTileImage(Tile tile, Func> loadImageFunc) 11 | { 12 | var tcs = new TaskCompletionSource(); 13 | 14 | async void LoadTileImage() 15 | { 16 | try 17 | { 18 | var image = await loadImageFunc(); 19 | 20 | tcs.TrySetResult(null); // tcs.Task has completed when image is loaded 21 | 22 | tile.SetImageSource(image); 23 | } 24 | catch (Exception ex) 25 | { 26 | tcs.TrySetException(ex); 27 | } 28 | } 29 | 30 | if (!await tile.Image.Dispatcher.TryRunAsync(CoreDispatcherPriority.Low, LoadTileImage)) 31 | { 32 | tcs.TrySetCanceled(); 33 | } 34 | 35 | await tcs.Task; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MapControl/WPF/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] 4 | -------------------------------------------------------------------------------- /MapControl/WPF/ImageLoader.WPF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using System.Windows; 5 | using System.Windows.Media; 6 | using System.Windows.Media.Imaging; 7 | 8 | namespace MapControl 9 | { 10 | public static partial class ImageLoader 11 | { 12 | public static ImageSource LoadImage(Uri uri) 13 | { 14 | return new BitmapImage(uri); 15 | } 16 | 17 | public static ImageSource LoadImage(Stream stream) 18 | { 19 | var image = new BitmapImage(); 20 | 21 | image.BeginInit(); 22 | image.CacheOption = BitmapCacheOption.OnLoad; 23 | image.StreamSource = stream; 24 | image.EndInit(); 25 | image.Freeze(); 26 | 27 | return image; 28 | } 29 | 30 | public static Task LoadImageAsync(Stream stream) 31 | { 32 | return Task.FromResult(LoadImage(stream)); 33 | } 34 | 35 | public static Task LoadImageAsync(string path) 36 | { 37 | if (!File.Exists(path)) 38 | { 39 | return Task.FromResult(null); 40 | } 41 | 42 | using (var stream = File.OpenRead(path)) 43 | { 44 | return LoadImageAsync(stream); 45 | } 46 | } 47 | 48 | internal static async Task LoadMergedImageAsync(Uri uri1, Uri uri2, IProgress progress) 49 | { 50 | WriteableBitmap mergedBitmap = null; 51 | var p1 = 0d; 52 | var p2 = 0d; 53 | 54 | var images = await Task.WhenAll( 55 | LoadImageAsync(uri1, new Progress(p => { p1 = p; progress.Report((p1 + p2) / 2d); })), 56 | LoadImageAsync(uri2, new Progress(p => { p2 = p; progress.Report((p1 + p2) / 2d); }))); 57 | 58 | if (images.Length == 2 && 59 | images[0] is BitmapSource bitmap1 && 60 | images[1] is BitmapSource bitmap2 && 61 | bitmap1.PixelHeight == bitmap2.PixelHeight && 62 | bitmap1.Format == bitmap2.Format && 63 | bitmap1.Format.BitsPerPixel % 8 == 0) 64 | { 65 | var format = bitmap1.Format; 66 | var height = bitmap1.PixelHeight; 67 | var width1 = bitmap1.PixelWidth; 68 | var width2 = bitmap2.PixelWidth; 69 | var stride1 = width1 * format.BitsPerPixel / 8; 70 | var stride2 = width2 * format.BitsPerPixel / 8; 71 | var buffer1 = new byte[stride1 * height]; 72 | var buffer2 = new byte[stride2 * height]; 73 | 74 | bitmap1.CopyPixels(buffer1, stride1, 0); 75 | bitmap2.CopyPixels(buffer2, stride2, 0); 76 | 77 | mergedBitmap = new WriteableBitmap(width1 + width2, height, 96, 96, format, null); 78 | mergedBitmap.WritePixels(new Int32Rect(0, 0, width1, height), buffer1, stride1, 0); 79 | mergedBitmap.WritePixels(new Int32Rect(width1, 0, width2, height), buffer2, stride2, 0); 80 | mergedBitmap.Freeze(); 81 | } 82 | 83 | return mergedBitmap; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /MapControl/WPF/LocationAnimation.WPF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Media.Animation; 4 | 5 | namespace MapControl 6 | { 7 | public class LocationAnimation : AnimationTimeline 8 | { 9 | public override Type TargetPropertyType => typeof(Location); 10 | 11 | public Location To { get; set; } 12 | 13 | public IEasingFunction EasingFunction { get; set; } 14 | 15 | protected override Freezable CreateInstanceCore() 16 | { 17 | return new LocationAnimation 18 | { 19 | To = To, 20 | EasingFunction = EasingFunction 21 | }; 22 | } 23 | 24 | public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock) 25 | { 26 | var from = (Location)defaultOriginValue; 27 | var progress = animationClock.CurrentProgress ?? 1d; 28 | 29 | if (EasingFunction != null) 30 | { 31 | progress = EasingFunction.Ease(progress); 32 | } 33 | 34 | return new Location( 35 | (1d - progress) * from.Latitude + progress * To.Latitude, 36 | (1d - progress) * from.Longitude + progress * To.Longitude); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MapControl/WPF/MapContentControl.WPF.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace MapControl 5 | { 6 | /// 7 | /// ContentControl placed on a MapPanel at a geographic location specified by the Location property. 8 | /// 9 | public class MapContentControl : ContentControl 10 | { 11 | public static readonly DependencyProperty AutoCollapseProperty = 12 | MapPanel.AutoCollapseProperty.AddOwner(typeof(MapContentControl)); 13 | 14 | public static readonly DependencyProperty LocationProperty = 15 | MapPanel.LocationProperty.AddOwner(typeof(MapContentControl)); 16 | 17 | static MapContentControl() 18 | { 19 | DefaultStyleKeyProperty.OverrideMetadata(typeof(MapContentControl), new FrameworkPropertyMetadata(typeof(MapContentControl))); 20 | } 21 | 22 | /// 23 | /// Gets/sets MapPanel.AutoCollapse. 24 | /// 25 | public bool AutoCollapse 26 | { 27 | get => (bool)GetValue(AutoCollapseProperty); 28 | set => SetValue(AutoCollapseProperty, value); 29 | } 30 | 31 | /// 32 | /// Gets/sets MapPanel.Location. 33 | /// 34 | public Location Location 35 | { 36 | get => (Location)GetValue(LocationProperty); 37 | set => SetValue(LocationProperty, value); 38 | } 39 | } 40 | 41 | /// 42 | /// MapContentControl with a Pushpin Style. 43 | /// 44 | public class Pushpin : MapContentControl 45 | { 46 | static Pushpin() 47 | { 48 | DefaultStyleKeyProperty.OverrideMetadata(typeof(Pushpin), new FrameworkPropertyMetadata(typeof(Pushpin))); 49 | } 50 | 51 | public static readonly DependencyProperty CornerRadiusProperty = 52 | DependencyPropertyHelper.Register(nameof(CornerRadius)); 53 | 54 | public CornerRadius CornerRadius 55 | { 56 | get => (CornerRadius)GetValue(CornerRadiusProperty); 57 | set => SetValue(CornerRadiusProperty, value); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MapControl/WPF/MapControl.WPF.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows;net462 4 | true 5 | WPF 6 | MapControl 7 | XAML Map Control Library for WPF 8 | $(GeneratePackage) 9 | XAML.MapControl.WPF 10 | $(AssemblyTitle) 11 | A set of WPF controls for rendering raster maps from different providers like OpenStreetMap and various types of map overlays 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /MapControl/WPF/MapImageLayer.WPF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Media.Animation; 4 | 5 | namespace MapControl 6 | { 7 | public partial class MapImageLayer 8 | { 9 | private void FadeOver() 10 | { 11 | var fadeInAnimation = new DoubleAnimation 12 | { 13 | To = 1d, 14 | Duration = MapBase.ImageFadeDuration 15 | }; 16 | 17 | var fadeOutAnimation = new DoubleAnimation 18 | { 19 | To = 0d, 20 | BeginTime = MapBase.ImageFadeDuration, 21 | Duration = TimeSpan.Zero 22 | }; 23 | 24 | Storyboard.SetTarget(fadeInAnimation, Children[1]); 25 | Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(OpacityProperty)); 26 | 27 | Storyboard.SetTarget(fadeOutAnimation, Children[0]); 28 | Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(OpacityProperty)); 29 | 30 | var storyboard = new Storyboard(); 31 | storyboard.Children.Add(fadeInAnimation); 32 | storyboard.Children.Add(fadeOutAnimation); 33 | storyboard.Begin(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MapControl/WPF/MapItem.WPF.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Input; 4 | 5 | namespace MapControl 6 | { 7 | public partial class MapItem 8 | { 9 | public static readonly DependencyProperty AutoCollapseProperty = 10 | MapPanel.AutoCollapseProperty.AddOwner(typeof(MapItem)); 11 | 12 | public static readonly DependencyProperty LocationProperty = 13 | MapPanel.LocationProperty.AddOwner(typeof(MapItem), 14 | new FrameworkPropertyMetadata(null, (o, e) => ((MapItem)o).UpdateMapTransform())); 15 | 16 | static MapItem() 17 | { 18 | DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItem), new FrameworkPropertyMetadata(typeof(MapItem))); 19 | } 20 | 21 | protected override void OnTouchDown(TouchEventArgs e) 22 | { 23 | e.Handled = true; 24 | } 25 | 26 | protected override void OnTouchUp(TouchEventArgs e) 27 | { 28 | e.Handled = true; 29 | } 30 | 31 | protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) 32 | { 33 | if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) && 34 | ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl && 35 | mapItemsControl.SelectionMode == SelectionMode.Extended) 36 | { 37 | mapItemsControl.SelectItemsInRange(this); 38 | e.Handled = true; 39 | } 40 | else 41 | { 42 | base.OnMouseLeftButtonDown(e); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MapControl/WPF/MapItemsControl.WPF.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Media; 4 | 5 | namespace MapControl 6 | { 7 | public partial class MapItemsControl 8 | { 9 | static MapItemsControl() 10 | { 11 | DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItemsControl), new FrameworkPropertyMetadata(typeof(MapItemsControl))); 12 | } 13 | 14 | public void SelectItemsInGeometry(Geometry geometry) 15 | { 16 | SelectItemsByPosition(geometry.FillContains); 17 | } 18 | 19 | public MapItem ContainerFromItem(object item) 20 | { 21 | return (MapItem)ItemContainerGenerator.ContainerFromItem(item); 22 | } 23 | 24 | public object ItemFromContainer(MapItem container) 25 | { 26 | return ItemContainerGenerator.ItemFromContainer(container); 27 | } 28 | 29 | protected override bool IsItemItsOwnContainerOverride(object item) 30 | { 31 | return item is MapItem; 32 | } 33 | 34 | protected override DependencyObject GetContainerForItemOverride() 35 | { 36 | return new MapItem(); 37 | } 38 | 39 | protected override void PrepareContainerForItemOverride(DependencyObject container, object item) 40 | { 41 | base.PrepareContainerForItemOverride(container, item); 42 | PrepareContainer(container, item); 43 | } 44 | 45 | protected override void ClearContainerForItemOverride(DependencyObject container, object item) 46 | { 47 | base.ClearContainerForItemOverride(container, item); 48 | ClearContainer(container); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /MapControl/WPF/MapPanel.WPF.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Windows; 4 | using System.Windows.Media; 5 | 6 | namespace MapControl 7 | { 8 | public partial class MapPanel 9 | { 10 | public static readonly DependencyProperty AutoCollapseProperty = 11 | DependencyPropertyHelper.RegisterAttached< bool>("AutoCollapse", typeof(MapPanel)); 12 | 13 | public static readonly DependencyProperty LocationProperty = 14 | DependencyPropertyHelper.RegisterAttached("Location", typeof(MapPanel), null, 15 | FrameworkPropertyMetadataOptions.AffectsParentArrange); 16 | 17 | public static readonly DependencyProperty BoundingBoxProperty = 18 | DependencyPropertyHelper.RegisterAttached("BoundingBox", typeof(MapPanel), null, 19 | FrameworkPropertyMetadataOptions.AffectsParentArrange); 20 | 21 | protected IEnumerable ChildElements => InternalChildren.OfType(); 22 | 23 | public static MapBase GetParentMap(FrameworkElement element) 24 | { 25 | return (MapBase)element.GetValue(ParentMapProperty); 26 | } 27 | 28 | public static void SetRenderTransform(FrameworkElement element, Transform transform, double originX = 0d, double originY = 0d) 29 | { 30 | element.RenderTransform = transform; 31 | element.RenderTransformOrigin = new Point(originX, originY); 32 | } 33 | 34 | private static void SetVisible(FrameworkElement element, bool visible) 35 | { 36 | element.Visibility = visible ? Visibility.Visible : Visibility.Collapsed; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MapControl/WPF/MapPath.WPF.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Media; 3 | using System.Windows.Shapes; 4 | 5 | namespace MapControl 6 | { 7 | public partial class MapPath : Shape 8 | { 9 | public static readonly DependencyProperty DataProperty = 10 | Path.DataProperty.AddOwner(typeof(MapPath), 11 | new FrameworkPropertyMetadata(null, (o, e) => ((MapPath)o).DataPropertyChanged(e))); 12 | 13 | public Geometry Data 14 | { 15 | get => (Geometry)GetValue(DataProperty); 16 | set => SetValue(DataProperty, value); 17 | } 18 | 19 | protected override Geometry DefiningGeometry => Data; 20 | 21 | private void DataPropertyChanged(DependencyPropertyChangedEventArgs e) 22 | { 23 | // Check if Data is actually a new Geometry. 24 | // 25 | if (e.NewValue != null && !ReferenceEquals(e.NewValue, e.OldValue)) 26 | { 27 | var data = (Geometry)e.NewValue; 28 | 29 | if (data.IsFrozen) 30 | { 31 | Data = data.Clone(); // DataPropertyChanged called again 32 | } 33 | else 34 | { 35 | UpdateData(); 36 | } 37 | } 38 | } 39 | 40 | private void SetMapTransform(Matrix matrix) 41 | { 42 | if (Data.Transform is MatrixTransform transform && !transform.IsFrozen) 43 | { 44 | transform.Matrix = matrix; 45 | } 46 | else 47 | { 48 | Data.Transform = new MatrixTransform(matrix); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /MapControl/WPF/PolygonCollection.WPF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Collections.Specialized; 5 | using System.Linq; 6 | using System.Windows; 7 | 8 | namespace MapControl 9 | { 10 | /// 11 | /// An ObservableCollection of IEnumerable of Location. PolygonCollection adds a CollectionChanged 12 | /// listener to each element that implements INotifyCollectionChanged and, when such an element changes, 13 | /// fires its own CollectionChanged event with NotifyCollectionChangedAction.Replace for that element. 14 | /// 15 | public class PolygonCollection : ObservableCollection>, IWeakEventListener 16 | { 17 | public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) 18 | { 19 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender)); 20 | 21 | return true; 22 | } 23 | 24 | protected override void InsertItem(int index, IEnumerable polygon) 25 | { 26 | if (polygon is INotifyCollectionChanged addedPolygon) 27 | { 28 | CollectionChangedEventManager.AddListener(addedPolygon, this); 29 | } 30 | 31 | base.InsertItem(index, polygon); 32 | } 33 | 34 | protected override void SetItem(int index, IEnumerable polygon) 35 | { 36 | if (this[index] is INotifyCollectionChanged removedPolygon) 37 | { 38 | CollectionChangedEventManager.RemoveListener(removedPolygon, this); 39 | } 40 | 41 | if (polygon is INotifyCollectionChanged addedPolygon) 42 | { 43 | CollectionChangedEventManager.AddListener(addedPolygon, this); 44 | } 45 | 46 | base.SetItem(index, polygon); 47 | } 48 | 49 | protected override void RemoveItem(int index) 50 | { 51 | if (this[index] is INotifyCollectionChanged removedPolygon) 52 | { 53 | CollectionChangedEventManager.RemoveListener(removedPolygon, this); 54 | } 55 | 56 | base.RemoveItem(index); 57 | } 58 | 59 | protected override void ClearItems() 60 | { 61 | foreach (var polygon in this.OfType()) 62 | { 63 | CollectionChangedEventManager.RemoveListener(polygon, this); 64 | } 65 | 66 | base.ClearItems(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /MapControl/WPF/Tile.WPF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Media; 5 | using System.Windows.Media.Animation; 6 | using System.Windows.Media.Imaging; 7 | 8 | namespace MapControl 9 | { 10 | public partial class Tile 11 | { 12 | private void BeginFadeInAnimation() 13 | { 14 | var fadeInAnimation = new DoubleAnimation 15 | { 16 | From = 0d, 17 | Duration = MapBase.ImageFadeDuration, 18 | FillBehavior = FillBehavior.Stop 19 | }; 20 | 21 | Image.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation); 22 | } 23 | 24 | private void FadeIn() 25 | { 26 | if (Image.Source is BitmapSource bitmap && !bitmap.IsFrozen && bitmap.IsDownloading) 27 | { 28 | bitmap.DownloadCompleted += BitmapDownloadCompleted; 29 | bitmap.DownloadFailed += BitmapDownloadFailed; 30 | } 31 | else 32 | { 33 | BeginFadeInAnimation(); 34 | } 35 | } 36 | 37 | private void BitmapDownloadCompleted(object sender, EventArgs e) 38 | { 39 | var bitmap = (BitmapSource)sender; 40 | 41 | bitmap.DownloadCompleted -= BitmapDownloadCompleted; 42 | bitmap.DownloadFailed -= BitmapDownloadFailed; 43 | 44 | BeginFadeInAnimation(); 45 | } 46 | 47 | private void BitmapDownloadFailed(object sender, ExceptionEventArgs e) 48 | { 49 | var bitmap = (BitmapSource)sender; 50 | 51 | bitmap.DownloadCompleted -= BitmapDownloadCompleted; 52 | bitmap.DownloadFailed -= BitmapDownloadFailed; 53 | 54 | Image.Source = null; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /MapControl/WPF/TileImageLoader.WPF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Windows.Media; 4 | 5 | namespace MapControl 6 | { 7 | public partial class TileImageLoader 8 | { 9 | private static async Task LoadTileImage(Tile tile, Func> loadImageFunc) 10 | { 11 | var image = await loadImageFunc().ConfigureAwait(false); 12 | 13 | _ = tile.Image.Dispatcher.InvokeAsync(() => tile.SetImageSource(image)); // no need to await InvokeAsync 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MapControl/WinUI/DependencyPropertyHelper.WinUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if UWP 3 | using Windows.UI.Xaml; 4 | #else 5 | using Microsoft.UI.Xaml; 6 | #endif 7 | 8 | #pragma warning disable IDE0060 // Remove unused parameter 9 | 10 | namespace MapControl 11 | { 12 | public static class DependencyPropertyHelper 13 | { 14 | public static DependencyProperty RegisterAttached( 15 | string name, 16 | Type ownerType, 17 | TValue defaultValue = default, 18 | Action changed = null, 19 | bool inherits = false) // unused in WinUI/UWP 20 | { 21 | var metadata = changed == null 22 | ? new PropertyMetadata(defaultValue) 23 | : new PropertyMetadata(defaultValue, (o, e) => 24 | { 25 | if (o is FrameworkElement element) 26 | { 27 | changed(element, (TValue)e.OldValue, (TValue)e.NewValue); 28 | } 29 | }); 30 | 31 | return DependencyProperty.RegisterAttached(name, typeof(TValue), ownerType, metadata); 32 | } 33 | 34 | public static DependencyProperty Register( 35 | string name, 36 | TValue defaultValue = default, 37 | Action changed = null) 38 | where TOwner : DependencyObject 39 | { 40 | var metadata = changed != null 41 | ? new PropertyMetadata(defaultValue, (o, e) => changed((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue)) 42 | : new PropertyMetadata(defaultValue); 43 | 44 | return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MapControl/WinUI/GeoImage.WinUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Windows.Graphics.Imaging; 4 | using Windows.Storage; 5 | #if UWP 6 | using Windows.UI.Xaml.Media.Imaging; 7 | #else 8 | using Microsoft.UI.Xaml.Media.Imaging; 9 | #endif 10 | 11 | namespace MapControl 12 | { 13 | public static partial class GeoImage 14 | { 15 | private static async Task LoadGeoTiff(string sourcePath) 16 | { 17 | BitmapSource bitmap; 18 | Matrix transform; 19 | MapProjection projection = null; 20 | 21 | var file = await StorageFile.GetFileFromPathAsync(FilePath.GetFullPath(sourcePath)); 22 | 23 | using (var stream = await file.OpenReadAsync()) 24 | { 25 | var decoder = await BitmapDecoder.CreateAsync(stream); 26 | 27 | bitmap = await ImageLoader.LoadWriteableBitmapAsync(decoder); 28 | 29 | var geoKeyDirectoryQuery = QueryString(GeoKeyDirectoryTag); 30 | var pixelScaleQuery = QueryString(ModelPixelScaleTag); 31 | var tiePointQuery = QueryString(ModelTiePointTag); 32 | var transformationQuery = QueryString(ModelTransformationTag); 33 | var metadata = await decoder.BitmapProperties.GetPropertiesAsync( 34 | new string[] 35 | { 36 | pixelScaleQuery, 37 | tiePointQuery, 38 | transformationQuery, 39 | geoKeyDirectoryQuery 40 | }); 41 | 42 | if (metadata.TryGetValue(pixelScaleQuery, out BitmapTypedValue pixelScaleValue) && 43 | pixelScaleValue.Value is double[] pixelScale && 44 | pixelScale.Length == 3 && 45 | metadata.TryGetValue(tiePointQuery, out BitmapTypedValue tiePointValue) && 46 | tiePointValue.Value is double[] tiePoint && 47 | tiePoint.Length >= 6) 48 | { 49 | transform = new Matrix(pixelScale[0], 0d, 0d, -pixelScale[1], tiePoint[3], tiePoint[4]); 50 | } 51 | else if (metadata.TryGetValue(transformationQuery, out BitmapTypedValue transformValue) && 52 | transformValue.Value is double[] transformValues && 53 | transformValues.Length == 16) 54 | { 55 | transform = new Matrix(transformValues[0], transformValues[1], 56 | transformValues[4], transformValues[5], 57 | transformValues[3], transformValues[7]); 58 | } 59 | else 60 | { 61 | throw new ArgumentException("No coordinate transformation found."); 62 | } 63 | 64 | if (metadata.TryGetValue(geoKeyDirectoryQuery, out BitmapTypedValue geoKeyDirValue) && 65 | geoKeyDirValue.Value is short[] geoKeyDirectory) 66 | { 67 | projection = GetProjection(geoKeyDirectory); 68 | } 69 | } 70 | 71 | return new GeoBitmap(bitmap, transform, projection); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /MapControl/WinUI/MapContentControl.WinUI.cs: -------------------------------------------------------------------------------- 1 | #if UWP 2 | using Windows.UI.Xaml; 3 | using Windows.UI.Xaml.Controls; 4 | using Windows.UI.Xaml.Data; 5 | #else 6 | using Microsoft.UI.Xaml; 7 | using Microsoft.UI.Xaml.Controls; 8 | using Microsoft.UI.Xaml.Data; 9 | #endif 10 | 11 | namespace MapControl 12 | { 13 | /// 14 | /// ContentControl placed on a MapPanel at a geographic location specified by the Location property. 15 | /// 16 | public class MapContentControl : ContentControl 17 | { 18 | public static readonly DependencyProperty AutoCollapseProperty = 19 | DependencyPropertyHelper.Register(nameof(AutoCollapse), false, 20 | (control, oldValue, newValue) => MapPanel.SetAutoCollapse(control, newValue)); 21 | 22 | public static readonly DependencyProperty LocationProperty = 23 | DependencyPropertyHelper.Register(nameof(Location), null, 24 | (control, oldValue, newValue) => MapPanel.SetLocation(control, newValue)); 25 | 26 | public MapContentControl() 27 | { 28 | DefaultStyleKey = typeof(MapContentControl); 29 | MapPanel.InitMapElement(this); 30 | } 31 | 32 | /// 33 | /// Gets/sets MapPanel.AutoCollapse. 34 | /// 35 | public bool AutoCollapse 36 | { 37 | get => (bool)GetValue(AutoCollapseProperty); 38 | set => SetValue(AutoCollapseProperty, value); 39 | } 40 | 41 | /// 42 | /// Gets/sets MapPanel.Location. 43 | /// 44 | public Location Location 45 | { 46 | get => (Location)GetValue(LocationProperty); 47 | set => SetValue(LocationProperty, value); 48 | } 49 | 50 | protected override void OnApplyTemplate() 51 | { 52 | base.OnApplyTemplate(); 53 | 54 | var parentMap = MapPanel.GetParentMap(this); 55 | 56 | if (parentMap != null) 57 | { 58 | // Workaround for missing RelativeSource AncestorType=MapBase Bindings in default Style. 59 | // 60 | if (Background == null) 61 | { 62 | SetBinding(BackgroundProperty, 63 | new Binding { Source = parentMap, Path = new PropertyPath(nameof(Background)) }); 64 | } 65 | if (Foreground == null) 66 | { 67 | SetBinding(ForegroundProperty, 68 | new Binding { Source = parentMap, Path = new PropertyPath(nameof(Foreground)) }); 69 | } 70 | if (BorderBrush == null) 71 | { 72 | SetBinding(BorderBrushProperty, 73 | new Binding { Source = parentMap, Path = new PropertyPath(nameof(Foreground)) }); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /// 80 | /// MapContentControl with a Pushpin Style. 81 | /// 82 | public class Pushpin : MapContentControl 83 | { 84 | public Pushpin() 85 | { 86 | DefaultStyleKey = typeof(Pushpin); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /MapControl/WinUI/MapControl.WinUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows10.0.17763.0 4 | win-x86;win-x64;win-arm64 5 | true 6 | WINUI 7 | MapControl 8 | XAML Map Control Library for WinUI 9 | $(GeneratePackage) 10 | XAML.MapControl.WinUI 11 | $(AssemblyTitle) 12 | A set of WinUI controls for rendering raster maps from different providers like OpenStreetMap and various types of map overlays 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /MapControl/WinUI/MapImageLayer.WinUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if UWP 3 | using Windows.UI.Xaml.Media.Animation; 4 | #else 5 | using Microsoft.UI.Xaml.Media.Animation; 6 | #endif 7 | 8 | namespace MapControl 9 | { 10 | public partial class MapImageLayer 11 | { 12 | private void FadeOver() 13 | { 14 | var fadeInAnimation = new DoubleAnimation 15 | { 16 | To = 1d, 17 | Duration = MapBase.ImageFadeDuration 18 | }; 19 | 20 | var fadeOutAnimation = new DoubleAnimation 21 | { 22 | To = 0d, 23 | BeginTime = MapBase.ImageFadeDuration, 24 | Duration = TimeSpan.Zero 25 | }; 26 | 27 | Storyboard.SetTarget(fadeInAnimation, Children[1]); 28 | Storyboard.SetTargetProperty(fadeInAnimation, nameof(Opacity)); 29 | 30 | Storyboard.SetTarget(fadeOutAnimation, Children[0]); 31 | Storyboard.SetTargetProperty(fadeOutAnimation, nameof(Opacity)); 32 | 33 | var storyboard = new Storyboard(); 34 | storyboard.Children.Add(fadeInAnimation); 35 | storyboard.Children.Add(fadeOutAnimation); 36 | storyboard.Begin(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MapControl/WinUI/MapItemsControl.WinUI.cs: -------------------------------------------------------------------------------- 1 | #if UWP 2 | using Windows.UI.Xaml; 3 | #else 4 | using Microsoft.UI.Xaml; 5 | #endif 6 | 7 | namespace MapControl 8 | { 9 | public partial class MapItemsControl 10 | { 11 | public MapItemsControl() 12 | { 13 | DefaultStyleKey = typeof(MapItemsControl); 14 | MapPanel.InitMapElement(this); 15 | } 16 | 17 | public new MapItem ContainerFromItem(object item) 18 | { 19 | return (MapItem)base.ContainerFromItem(item); 20 | } 21 | 22 | protected override bool IsItemItsOwnContainerOverride(object item) 23 | { 24 | return item is MapItem; 25 | } 26 | 27 | protected override DependencyObject GetContainerForItemOverride() 28 | { 29 | return new MapItem(); 30 | } 31 | 32 | protected override void PrepareContainerForItemOverride(DependencyObject container, object item) 33 | { 34 | base.PrepareContainerForItemOverride(container, item); 35 | PrepareContainer(container, item); 36 | } 37 | 38 | protected override void ClearContainerForItemOverride(DependencyObject container, object item) 39 | { 40 | base.ClearContainerForItemOverride(container, item); 41 | ClearContainer(container); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /MapControl/WinUI/MapPanel.WinUI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | #if UWP 4 | using Windows.UI.Xaml; 5 | using Windows.UI.Xaml.Media; 6 | #else 7 | using Microsoft.UI.Xaml; 8 | using Microsoft.UI.Xaml.Media; 9 | #endif 10 | 11 | namespace MapControl 12 | { 13 | public partial class MapPanel 14 | { 15 | public static readonly DependencyProperty AutoCollapseProperty = 16 | DependencyPropertyHelper.RegisterAttached("AutoCollapse", typeof(MapPanel)); 17 | 18 | public static readonly DependencyProperty LocationProperty = 19 | DependencyPropertyHelper.RegisterAttached("Location", typeof(MapPanel), null, 20 | (element, oldValue, newValue) => (element.Parent as MapPanel)?.InvalidateArrange()); 21 | 22 | public static readonly DependencyProperty BoundingBoxProperty = 23 | DependencyPropertyHelper.RegisterAttached("BoundingBox", typeof(MapPanel), null, 24 | (element, oldValue, newValue) => (element.Parent as MapPanel)?.InvalidateArrange()); 25 | 26 | protected IEnumerable ChildElements => Children.OfType(); 27 | 28 | partial void InitMapPanel() 29 | { 30 | InitMapElement(this); 31 | } 32 | 33 | public static void InitMapElement(FrameworkElement element) 34 | { 35 | // Workaround for missing property value inheritance. 36 | // Loaded and Unloaded handlers set and clear the ParentMap property value. 37 | // 38 | element.Loaded += (s, e) => GetParentMap((FrameworkElement)s); 39 | element.Unloaded += (s, e) => ((FrameworkElement)s).ClearValue(ParentMapProperty); 40 | } 41 | 42 | public static MapBase GetParentMap(FrameworkElement element) 43 | { 44 | var parentMap = (MapBase)element.GetValue(ParentMapProperty); 45 | 46 | // Traverse visual tree because of missing property value inheritance. 47 | // 48 | if (parentMap == null && 49 | VisualTreeHelper.GetParent(element) is FrameworkElement parentElement) 50 | { 51 | parentMap = (parentElement as MapBase) ?? GetParentMap(parentElement); 52 | 53 | if (parentMap != null) 54 | { 55 | element.SetValue(ParentMapProperty, parentMap); 56 | } 57 | } 58 | 59 | return parentMap; 60 | } 61 | 62 | public static void SetRenderTransform(FrameworkElement element, Transform transform, double originX = 0d, double originY = 0d) 63 | { 64 | element.RenderTransform = transform; 65 | element.RenderTransformOrigin = new Point(originX, originY); 66 | } 67 | 68 | private static void SetVisible(FrameworkElement element, bool visible) 69 | { 70 | element.Visibility = visible ? Visibility.Visible : Visibility.Collapsed; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /MapControl/WinUI/MapPath.WinUI.cs: -------------------------------------------------------------------------------- 1 | #if UWP 2 | using Windows.UI.Xaml.Media; 3 | using Windows.UI.Xaml.Shapes; 4 | #else 5 | using Microsoft.UI.Xaml.Media; 6 | using Microsoft.UI.Xaml.Shapes; 7 | #endif 8 | 9 | namespace MapControl 10 | { 11 | public partial class MapPath : Path 12 | { 13 | public MapPath() 14 | { 15 | MapPanel.InitMapElement(this); 16 | } 17 | 18 | private void SetMapTransform(Matrix matrix) 19 | { 20 | if (Data.Transform is MatrixTransform transform) 21 | { 22 | transform.Matrix = matrix; 23 | } 24 | else 25 | { 26 | Data.Transform = new MatrixTransform { Matrix = matrix }; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MapControl/WinUI/Point.WinUI.cs: -------------------------------------------------------------------------------- 1 | namespace MapControl 2 | { 3 | /// 4 | /// Replaces Windows.Foundation.Point for double floating point precision. 5 | /// 6 | public readonly struct Point 7 | { 8 | public Point(double x, double y) 9 | { 10 | X = x; 11 | Y = y; 12 | } 13 | 14 | public double X { get; } 15 | public double Y { get; } 16 | 17 | public static implicit operator Windows.Foundation.Point(Point p) 18 | { 19 | return new Windows.Foundation.Point(p.X, p.Y); 20 | } 21 | 22 | public static implicit operator Point(Windows.Foundation.Point p) 23 | { 24 | return new Point(p.X, p.Y); 25 | } 26 | 27 | public static bool operator ==(Point p1, Point p2) 28 | { 29 | return p1.X == p2.X && p1.Y == p2.Y; 30 | } 31 | 32 | public static bool operator !=(Point p1, Point p2) 33 | { 34 | return !(p1 == p2); 35 | } 36 | 37 | public override bool Equals(object obj) 38 | { 39 | return obj is Point p && this == p; 40 | } 41 | 42 | public override int GetHashCode() 43 | { 44 | return X.GetHashCode() ^ Y.GetHashCode(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MapControl/WinUI/PushpinBorder.WinUI.cs: -------------------------------------------------------------------------------- 1 | using Windows.Foundation; 2 | #if UWP 3 | using Windows.UI.Xaml; 4 | using Windows.UI.Xaml.Controls; 5 | using Windows.UI.Xaml.Data; 6 | using Windows.UI.Xaml.Markup; 7 | using Windows.UI.Xaml.Media; 8 | using Windows.UI.Xaml.Shapes; 9 | #else 10 | using Microsoft.UI.Xaml; 11 | using Microsoft.UI.Xaml.Controls; 12 | using Microsoft.UI.Xaml.Data; 13 | using Microsoft.UI.Xaml.Markup; 14 | using Microsoft.UI.Xaml.Media; 15 | using Microsoft.UI.Xaml.Shapes; 16 | #endif 17 | 18 | namespace MapControl 19 | { 20 | [ContentProperty(Name = "Child")] 21 | public partial class PushpinBorder : UserControl 22 | { 23 | public static readonly DependencyProperty ArrowSizeProperty = 24 | DependencyPropertyHelper.Register(nameof(ArrowSize), new Size(10d, 20d), 25 | (border, oldValue, newValue) => border.SetBorderMargin()); 26 | 27 | public static readonly DependencyProperty BorderWidthProperty = 28 | DependencyPropertyHelper.Register(nameof(BorderWidth), 0d, 29 | (border, oldValue, newValue) => border.SetBorderMargin()); 30 | 31 | private readonly Border border = new Border(); 32 | 33 | public PushpinBorder() 34 | { 35 | var path = new Path 36 | { 37 | HorizontalAlignment = HorizontalAlignment.Stretch, 38 | VerticalAlignment = VerticalAlignment.Stretch, 39 | Stretch = Stretch.None 40 | }; 41 | 42 | path.SetBinding(Shape.FillProperty, 43 | new Binding { Source = this, Path = new PropertyPath(nameof(Background)) }); 44 | 45 | path.SetBinding(Shape.StrokeProperty, 46 | new Binding { Source = this, Path = new PropertyPath(nameof(BorderBrush)) }); 47 | 48 | path.SetBinding(Shape.StrokeThicknessProperty, 49 | new Binding { Source = this, Path = new PropertyPath(nameof(BorderWidth)) }); 50 | 51 | border.SetBinding(PaddingProperty, 52 | new Binding { Source = this, Path = new PropertyPath(nameof(Padding)) }); 53 | 54 | SetBorderMargin(); 55 | 56 | var grid = new Grid(); 57 | grid.Children.Add(path); 58 | grid.Children.Add(border); 59 | 60 | Content = grid; 61 | 62 | SizeChanged += (s, e) => path.Data = BuildGeometry(); 63 | } 64 | 65 | public UIElement Child 66 | { 67 | get => border.Child; 68 | set => border.Child = value; 69 | } 70 | 71 | private void SetBorderMargin() 72 | { 73 | border.Margin = new Thickness( 74 | BorderWidth, BorderWidth, BorderWidth, BorderWidth + ArrowSize.Height); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /MapControl/WinUI/Rect.WinUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl 4 | { 5 | /// 6 | /// Replaces Windows.Foundation.Rect for double floating point precision. 7 | /// 8 | public readonly struct Rect 9 | { 10 | public Rect(double x, double y, double width, double height) 11 | { 12 | X = x; 13 | Y = y; 14 | Width = width; 15 | Height = height; 16 | } 17 | 18 | public Rect(Point p1, Point p2) 19 | { 20 | X = Math.Min(p1.X, p2.X); 21 | Y = Math.Min(p1.Y, p2.Y); 22 | Width = Math.Max(p1.X, p2.X) - X; 23 | Height = Math.Max(p1.Y, p2.Y) - Y; 24 | } 25 | 26 | public double X { get; } 27 | public double Y { get; } 28 | public double Width { get; } 29 | public double Height { get; } 30 | 31 | public bool Contains(Point p) 32 | { 33 | return p.X >= X && p.X <= X + Width && p.Y >= Y && p.Y <= Y + Height; 34 | } 35 | 36 | public static implicit operator Windows.Foundation.Rect(Rect r) 37 | { 38 | return new Windows.Foundation.Rect(r.X, r.Y, r.Width, r.Height); 39 | } 40 | 41 | public static implicit operator Rect(Windows.Foundation.Rect r) 42 | { 43 | return new Rect(r.X, r.Y, r.Width, r.Height); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MapControl/WinUI/Tile.WinUI.cs: -------------------------------------------------------------------------------- 1 | #if UWP 2 | using Windows.UI.Xaml; 3 | using Windows.UI.Xaml.Media.Animation; 4 | using Windows.UI.Xaml.Media.Imaging; 5 | #else 6 | using Microsoft.UI.Xaml; 7 | using Microsoft.UI.Xaml.Media.Animation; 8 | using Microsoft.UI.Xaml.Media.Imaging; 9 | #endif 10 | 11 | namespace MapControl 12 | { 13 | public partial class Tile 14 | { 15 | private void BeginFadeInAnimation() 16 | { 17 | var fadeInAnimation = new DoubleAnimation 18 | { 19 | From = 0d, 20 | Duration = MapBase.ImageFadeDuration, 21 | FillBehavior = FillBehavior.Stop 22 | }; 23 | 24 | Storyboard.SetTarget(fadeInAnimation, Image); 25 | Storyboard.SetTargetProperty(fadeInAnimation, nameof(UIElement.Opacity)); 26 | 27 | var storyboard = new Storyboard(); 28 | storyboard.Children.Add(fadeInAnimation); 29 | storyboard.Begin(); 30 | } 31 | 32 | private void FadeIn() 33 | { 34 | if (Image.Source is BitmapImage bitmap && bitmap.UriSource != null) 35 | { 36 | bitmap.ImageOpened += BitmapImageOpened; 37 | bitmap.ImageFailed += BitmapImageFailed; 38 | } 39 | else 40 | { 41 | BeginFadeInAnimation(); 42 | } 43 | } 44 | 45 | private void BitmapImageOpened(object sender, RoutedEventArgs e) 46 | { 47 | var bitmap = (BitmapImage)sender; 48 | 49 | bitmap.ImageOpened -= BitmapImageOpened; 50 | bitmap.ImageFailed -= BitmapImageFailed; 51 | 52 | BeginFadeInAnimation(); 53 | } 54 | 55 | private void BitmapImageFailed(object sender, ExceptionRoutedEventArgs e) 56 | { 57 | var bitmap = (BitmapImage)sender; 58 | 59 | bitmap.ImageOpened -= BitmapImageOpened; 60 | bitmap.ImageFailed -= BitmapImageFailed; 61 | 62 | Image.Source = null; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /MapControl/WinUI/TileImageLoader.WinUI.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Dispatching; 2 | using Microsoft.UI.Xaml.Media; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace MapControl 7 | { 8 | public partial class TileImageLoader 9 | { 10 | private static Task LoadTileImage(Tile tile, Func> loadImageFunc) 11 | { 12 | var tcs = new TaskCompletionSource(); 13 | 14 | async void LoadTileImage() 15 | { 16 | try 17 | { 18 | var image = await loadImageFunc(); 19 | 20 | tcs.TrySetResult(); // tcs.Task has completed when image is loaded 21 | 22 | tile.SetImageSource(image); 23 | } 24 | catch (Exception ex) 25 | { 26 | tcs.TrySetException(ex); 27 | } 28 | } 29 | 30 | if (!tile.Image.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, LoadTileImage)) 31 | { 32 | tcs.TrySetCanceled(); 33 | } 34 | 35 | return tcs.Task; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MapProjections/Avalonia/MapProjections.Avalonia.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | AVALONIA 5 | MapControl.Projections 6 | XAML Map Control Projections Library for Avalonia UI 7 | $(GeneratePackage) 8 | XAML.MapControl.MapProjections.Avalonia 9 | $(AssemblyTitle) 10 | Map projections library for XAML Map Control, based on ProjNET4GeoAPI 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MapProjections/Shared/Ed50UtmProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl.Projections 4 | { 5 | /// 6 | /// ED50 UTM Projection with zone number. 7 | /// 8 | public class Ed50UtmProjection : GeoApiProjection 9 | { 10 | public const int FirstZone = 28; 11 | public const int LastZone = 38; 12 | public const int FirstZoneEpsgCode = 23000 + FirstZone; 13 | public const int LastZoneEpsgCode = 23000 + LastZone; 14 | 15 | public int Zone { get; } 16 | 17 | public Ed50UtmProjection(int zone) 18 | { 19 | if (zone < FirstZone || zone > LastZone) 20 | { 21 | throw new ArgumentException($"Invalid ED50 UTM zone {zone}.", nameof(zone)); 22 | } 23 | 24 | Zone = zone; 25 | CoordinateSystemWkt 26 | = $"PROJCS[\"ED50 / UTM zone {zone}N\"," 27 | + "GEOGCS[\"ED50\"," 28 | + "DATUM[\"European_Datum_1950\"," 29 | + "SPHEROID[\"International 1924\",6378388,297],TOWGS84[-87,-98,-121,0,0,0,0]]," 30 | + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," 31 | + "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," 32 | + "AUTHORITY[\"EPSG\",\"4230\"]]," 33 | + "PROJECTION[\"Transverse_Mercator\"]," 34 | + "PARAMETER[\"latitude_of_origin\",0]," 35 | + $"PARAMETER[\"central_meridian\",{6 * zone - 183}]," 36 | + "PARAMETER[\"scale_factor\",0.9996]," 37 | + "PARAMETER[\"false_easting\",500000]," 38 | + "PARAMETER[\"false_northing\",0]," 39 | + "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," 40 | + "AXIS[\"Easting\",EAST]," 41 | + "AXIS[\"Northing\",NORTH]," 42 | + $"AUTHORITY[\"EPSG\",\"230{zone:00}\"]]"; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /MapProjections/Shared/Etrs89UtmProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl.Projections 4 | { 5 | /// 6 | /// ETRS89 UTM Projection with zone number. 7 | /// 8 | public class Etrs89UtmProjection : GeoApiProjection 9 | { 10 | public const int FirstZone = 28; 11 | public const int LastZone = 38; 12 | public const int FirstZoneEpsgCode = 25800 + FirstZone; 13 | public const int LastZoneEpsgCode = 25800 + LastZone; 14 | 15 | public int Zone { get; } 16 | 17 | public Etrs89UtmProjection(int zone) 18 | { 19 | if (zone < FirstZone || zone > LastZone) 20 | { 21 | throw new ArgumentException($"Invalid ETRS89 UTM zone {zone}.", nameof(zone)); 22 | } 23 | 24 | Zone = zone; 25 | CoordinateSystemWkt 26 | = $"PROJCS[\"ETRS89 / UTM zone {zone}N\"," 27 | + "GEOGCS[\"ETRS89\"," 28 | + "DATUM[\"European_Terrestrial_Reference_System_1989\"," 29 | + "SPHEROID[\"GRS 1980\",6378137,298.257222101]]," 30 | + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," 31 | + "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," 32 | + "AUTHORITY[\"EPSG\",\"4258\"]]," 33 | + "PROJECTION[\"Transverse_Mercator\"]," 34 | + "PARAMETER[\"latitude_of_origin\",0]," 35 | + $"PARAMETER[\"central_meridian\",{6 * zone - 183}]," 36 | + "PARAMETER[\"scale_factor\",0.9996]," 37 | + "PARAMETER[\"false_easting\",500000]," 38 | + "PARAMETER[\"false_northing\",0]," 39 | + "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," 40 | + "AXIS[\"Easting\",EAST]," 41 | + "AXIS[\"Northing\",NORTH]," 42 | + $"AUTHORITY[\"EPSG\",\"258{zone:00}\"]]"; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /MapProjections/Shared/GeoApiProjectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MapControl.Projections 4 | { 5 | public class GeoApiProjectionFactory : MapProjectionFactory 6 | { 7 | public static GeoApiProjectionFactory GetInstance() 8 | { 9 | if (!(Instance is GeoApiProjectionFactory factory)) 10 | { 11 | factory = new GeoApiProjectionFactory(); 12 | Instance = factory; 13 | } 14 | 15 | return factory; 16 | } 17 | 18 | public override MapProjection GetProjection(string crsId) 19 | { 20 | switch (crsId) 21 | { 22 | case MapControl.WebMercatorProjection.DefaultCrsId: 23 | return new WebMercatorProjection(); 24 | 25 | case MapControl.WorldMercatorProjection.DefaultCrsId: 26 | return new WorldMercatorProjection(); 27 | 28 | case MapControl.Wgs84AutoUtmProjection.DefaultCrsId: 29 | return new Wgs84AutoUtmProjection(); 30 | 31 | default: 32 | return base.GetProjection(crsId); 33 | } 34 | } 35 | 36 | public override MapProjection GetProjection(int epsgCode) 37 | { 38 | switch (epsgCode) 39 | { 40 | case int c when c >= Ed50UtmProjection.FirstZoneEpsgCode && c <= Ed50UtmProjection.LastZoneEpsgCode: 41 | return new Ed50UtmProjection(epsgCode % 100); 42 | 43 | case var c when c >= Etrs89UtmProjection.FirstZoneEpsgCode && c <= Etrs89UtmProjection.LastZoneEpsgCode: 44 | return new Etrs89UtmProjection(epsgCode % 100); 45 | 46 | case var c when c >= Nad27UtmProjection.FirstZoneEpsgCode && c <= Nad27UtmProjection.LastZoneEpsgCode: 47 | return new Nad27UtmProjection(epsgCode % 100); 48 | 49 | case var c when c >= Nad83UtmProjection.FirstZoneEpsgCode && c <= Nad83UtmProjection.LastZoneEpsgCode: 50 | return new Nad83UtmProjection(epsgCode % 100); 51 | 52 | case var c when c >= Wgs84UtmProjection.FirstZoneNorthEpsgCode && c <= Wgs84UtmProjection.LastZoneNorthEpsgCode: 53 | return new Wgs84UtmProjection(epsgCode % 100, true); 54 | 55 | case var c when c >= Wgs84UtmProjection.FirstZoneSouthEpsgCode && c <= Wgs84UtmProjection.LastZoneSouthEpsgCode: 56 | return new Wgs84UtmProjection(epsgCode % 100, false); 57 | 58 | default: 59 | return CoordinateSystemWkts.TryGetValue(epsgCode, out string wkt) 60 | ? new GeoApiProjection(wkt) 61 | : base.GetProjection(epsgCode); 62 | } 63 | } 64 | 65 | public Dictionary CoordinateSystemWkts { get; } = new Dictionary(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /MapProjections/Shared/Nad27UtmProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl.Projections 4 | { 5 | /// 6 | /// NAD27 UTM Projection with zone number. 7 | /// Appears to be less accurate than MapControl.Nad27UtmProjection. 8 | /// 9 | public class Nad27UtmProjection : GeoApiProjection 10 | { 11 | public const int FirstZone = 1; 12 | public const int LastZone = 22; 13 | public const int FirstZoneEpsgCode = 26700 + FirstZone; 14 | public const int LastZoneEpsgCode = 26700 + LastZone; 15 | 16 | public int Zone { get; } 17 | 18 | public Nad27UtmProjection(int zone) 19 | { 20 | if (zone < FirstZone || zone > LastZone) 21 | { 22 | throw new ArgumentException($"Invalid NAD27 UTM zone {zone}.", nameof(zone)); 23 | } 24 | 25 | Zone = zone; 26 | CoordinateSystemWkt 27 | = $"PROJCS[\"NAD27 / UTM zone {zone}N\"," 28 | + "GEOGCS[\"NAD27\"," 29 | + "DATUM[\"North_American_Datum_1927\"," 30 | + "SPHEROID[\"Clarke 1866\",6378206.4,294.978698213898]]," 31 | + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," 32 | + "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," 33 | + "AUTHORITY[\"EPSG\",\"4267\"]]," 34 | + "PROJECTION[\"Transverse_Mercator\"]," 35 | + "PARAMETER[\"latitude_of_origin\",0]," 36 | + $"PARAMETER[\"central_meridian\",{6 * zone - 183}]," 37 | + "PARAMETER[\"scale_factor\",0.9996]," 38 | + "PARAMETER[\"false_easting\",500000]," 39 | + "PARAMETER[\"false_northing\",0]," 40 | + "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," 41 | + "AXIS[\"Easting\",EAST]," 42 | + "AXIS[\"Northing\",NORTH]," 43 | + $"AUTHORITY[\"EPSG\",\"267{zone:00}\"]]"; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MapProjections/Shared/Nad83UtmProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MapControl.Projections 4 | { 5 | /// 6 | /// NAD83 UTM Projection with zone number. 7 | /// 8 | public class Nad83UtmProjection : GeoApiProjection 9 | { 10 | public const int FirstZone = 1; 11 | public const int LastZone = 23; 12 | public const int FirstZoneEpsgCode = 26900 + FirstZone; 13 | public const int LastZoneEpsgCode = 26900 + LastZone; 14 | 15 | public int Zone { get; } 16 | 17 | public Nad83UtmProjection(int zone) 18 | { 19 | if (zone < FirstZone || zone > LastZone) 20 | { 21 | throw new ArgumentException($"Invalid NAD83 UTM zone {zone}.", nameof(zone)); 22 | } 23 | 24 | Zone = zone; 25 | CoordinateSystemWkt 26 | = $"PROJCS[\"NAD83 / UTM zone {zone}N\"," 27 | + "GEOGCS[\"NAD83\"," 28 | + "DATUM[\"North_American_Datum_1983\"," 29 | + "SPHEROID[\"GRS 1980\",6378137,298.257222101]]," 30 | + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," 31 | + "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," 32 | + "AUTHORITY[\"EPSG\",\"4269\"]]," 33 | + "PROJECTION[\"Transverse_Mercator\"]," 34 | + "PARAMETER[\"latitude_of_origin\",0]," 35 | + $"PARAMETER[\"central_meridian\",{6 * zone - 183}]," 36 | + "PARAMETER[\"scale_factor\",0.9996]," 37 | + "PARAMETER[\"false_easting\",500000]," 38 | + "PARAMETER[\"false_northing\",0]," 39 | + "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," 40 | + "AXIS[\"Easting\",EAST]," 41 | + "AXIS[\"Northing\",NORTH]," 42 | + $"AUTHORITY[\"EPSG\",\"269{zone:00}\"]]"; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /MapProjections/Shared/WebMercatorProjection.cs: -------------------------------------------------------------------------------- 1 | using ProjNet.CoordinateSystems; 2 | using System; 3 | #if WPF 4 | using System.Windows; 5 | #elif AVALONIA 6 | using Avalonia; 7 | #endif 8 | 9 | namespace MapControl.Projections 10 | { 11 | /// 12 | /// Spherical Mercator Projection implemented by setting the CoordinateSystem property of a GeoApiProjection. 13 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/pp/1395/report.pdf), p.41-44. 14 | /// 15 | public class WebMercatorProjection : GeoApiProjection 16 | { 17 | public WebMercatorProjection() 18 | { 19 | CoordinateSystem = ProjectedCoordinateSystem.WebMercator; 20 | } 21 | 22 | public override Point GetRelativeScale(Location location) 23 | { 24 | var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3) 25 | 26 | return new Point(k, k); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MapProjections/Shared/Wgs84UtmProjection.cs: -------------------------------------------------------------------------------- 1 | using ProjNet.CoordinateSystems; 2 | using System; 3 | 4 | namespace MapControl.Projections 5 | { 6 | /// 7 | /// WGS84 UTM Projection with zone number and north/south flag. 8 | /// 9 | public class Wgs84UtmProjection : GeoApiProjection 10 | { 11 | public const int FirstZone = 1; 12 | public const int LastZone = 60; 13 | public const int FirstZoneNorthEpsgCode = 32600 + FirstZone; 14 | public const int LastZoneNorthEpsgCode = 32600 + LastZone; 15 | public const int FirstZoneSouthEpsgCode = 32700 + FirstZone; 16 | public const int LastZoneSouthEpsgCode = 32700 + LastZone; 17 | 18 | public int Zone { get; private set; } 19 | public bool IsNorth { get; private set; } 20 | 21 | public Wgs84UtmProjection(int zone, bool north) 22 | { 23 | SetZone(zone, north); 24 | } 25 | 26 | protected void SetZone(int zone, bool north) 27 | { 28 | if (zone < FirstZone || zone > LastZone) 29 | { 30 | throw new ArgumentException($"Invalid WGS84 UTM zone {zone}.", nameof(zone)); 31 | } 32 | 33 | Zone = zone; 34 | IsNorth = north; 35 | CoordinateSystem = ProjectedCoordinateSystem.WGS84_UTM(Zone, IsNorth); 36 | } 37 | } 38 | 39 | /// 40 | /// WGS84 UTM Projection with automatic zone selection from projection center. 41 | /// 42 | public class Wgs84AutoUtmProjection : Wgs84UtmProjection 43 | { 44 | private readonly string autoCrsId; 45 | 46 | public Wgs84AutoUtmProjection(string crsId = MapControl.Wgs84AutoUtmProjection.DefaultCrsId) 47 | : base(31, true) 48 | { 49 | autoCrsId = crsId; 50 | 51 | if (!string.IsNullOrEmpty(autoCrsId)) 52 | { 53 | CrsId = autoCrsId; 54 | } 55 | } 56 | 57 | public override Location Center 58 | { 59 | get => base.Center; 60 | protected set 61 | { 62 | if (!Equals(base.Center, value)) 63 | { 64 | base.Center = value; 65 | 66 | var lon = Location.NormalizeLongitude(value.Longitude); 67 | var zone = (int)Math.Floor(lon / 6d) + 31; 68 | var north = value.Latitude >= 0d; 69 | 70 | if (Zone != zone || IsNorth != north) 71 | { 72 | SetZone(zone, north); 73 | 74 | if (!string.IsNullOrEmpty(autoCrsId)) 75 | { 76 | CrsId = autoCrsId; 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /MapProjections/Shared/WorldMercatorProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if WPF 3 | using System.Windows; 4 | #elif AVALONIA 5 | using Avalonia; 6 | #endif 7 | 8 | namespace MapControl.Projections 9 | { 10 | /// 11 | /// Elliptical Mercator Projection implemented by setting the WKT property of a GeoApiProjection. 12 | /// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/pp/1395/report.pdf), p.44-45. 13 | /// 14 | public class WorldMercatorProjection : GeoApiProjection 15 | { 16 | public WorldMercatorProjection() 17 | { 18 | CoordinateSystemWkt 19 | = "PROJCS[\"WGS 84 / World Mercator\"," 20 | + "GEOGCS[\"WGS 84\"," 21 | + "DATUM[\"WGS_1984\"," 22 | + "SPHEROID[\"WGS 84\",6378137,298.257223563," 23 | + "AUTHORITY[\"EPSG\",\"7030\"]]," 24 | + "AUTHORITY[\"EPSG\",\"6326\"]]," 25 | + "PRIMEM[\"Greenwich\",0," 26 | + "AUTHORITY[\"EPSG\",\"8901\"]]," 27 | + "UNIT[\"degree\",0.0174532925199433," 28 | + "AUTHORITY[\"EPSG\",\"9122\"]]," 29 | + "AUTHORITY[\"EPSG\",\"4326\"]]," 30 | + "PROJECTION[\"Mercator_1SP\"]," 31 | + "PARAMETER[\"latitude_of_origin\",0]," 32 | + "PARAMETER[\"central_meridian\",0]," 33 | + "PARAMETER[\"scale_factor\",1]," 34 | + "PARAMETER[\"false_easting\",0]," 35 | + "PARAMETER[\"false_northing\",0]," 36 | + "UNIT[\"metre\",1," 37 | + "AUTHORITY[\"EPSG\",\"9001\"]]," 38 | + "AXIS[\"Easting\",EAST]," 39 | + "AXIS[\"Northing\",NORTH]," 40 | + "AUTHORITY[\"EPSG\",\"3395\"]]"; 41 | } 42 | 43 | public override Point GetRelativeScale(Location location) 44 | { 45 | var lat = location.Latitude * Math.PI / 180d; 46 | var eSinLat = Wgs84Eccentricity * Math.Sin(lat); 47 | var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8) 48 | 49 | return new Point(k, k); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /MapProjections/UWP/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("XAML Map Control Projections Library for UWP")] 5 | [assembly: AssemblyProduct("XAML Map Control")] 6 | [assembly: AssemblyCompany("Clemens Fischer")] 7 | [assembly: AssemblyCopyright("Copyright © 2024 Clemens Fischer")] 8 | [assembly: AssemblyTrademark("")] 9 | [assembly: AssemblyVersion("13.0.0")] 10 | [assembly: AssemblyFileVersion("13.0.0")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCulture("")] 13 | [assembly: ComVisible(false)] 14 | -------------------------------------------------------------------------------- /MapProjections/UWP/Properties/MapProjections.UWP.rd.xml: -------------------------------------------------------------------------------- 1 | 2 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /MapProjections/WPF/MapProjections.WPF.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows;net462 4 | true 5 | WPF 6 | MapControl.Projections 7 | XAML Map Control Projections Library for WPF 8 | $(GeneratePackage) 9 | XAML.MapControl.MapProjections.WPF 10 | $(AssemblyTitle) 11 | Map projections library for XAML Map Control, based on ProjNET4GeoAPI 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /MapProjections/WinUI/MapProjections.WinUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows10.0.17763.0 4 | win-x86;win-x64;win-arm64 5 | true 6 | WINUI 7 | MapControl.Projections 8 | XAML Map Control Projections Library for WinUI 9 | $(GeneratePackage) 10 | XAML.MapControl.MapProjections.WinUI 11 | $(AssemblyTitle) 12 | Map projections library for XAML Map Control, based on ProjNET4GeoAPI 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /MapUiTools/Avalonia/MapMenuItem.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Media; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace MapControl.UiTools 10 | { 11 | public abstract partial class MapMenuItem : MenuItem 12 | { 13 | protected MapMenuItem() 14 | { 15 | Icon = new TextBlock 16 | { 17 | FontFamily = new("Segoe MDL2 Assets"), 18 | FontWeight = FontWeight.Black, 19 | VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, 20 | }; 21 | 22 | Loaded += (s, e) => 23 | { 24 | if (DataContext is MapBase map) 25 | { 26 | IsChecked = GetIsChecked(map); 27 | } 28 | }; 29 | 30 | Click += async (s, e) => 31 | { 32 | if (DataContext is MapBase map) 33 | { 34 | await Execute(map); 35 | 36 | foreach (var item in ParentMenuItems) 37 | { 38 | item.IsChecked = item.GetIsChecked(map); 39 | } 40 | } 41 | }; 42 | } 43 | 44 | public string Text 45 | { 46 | get => Header as string; 47 | set => Header = value; 48 | } 49 | 50 | protected IEnumerable ParentMenuItems => ((ItemsControl)Parent).Items.OfType(); 51 | 52 | protected override Type StyleKeyOverride => typeof(MenuItem); 53 | 54 | protected abstract bool GetIsChecked(MapBase map); 55 | 56 | public abstract Task Execute(MapBase map); 57 | 58 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args) 59 | { 60 | base.OnPropertyChanged(args); 61 | 62 | if (args.Property == IsCheckedProperty) 63 | { 64 | ((TextBlock)Icon).Text = (bool)args.NewValue ? "\uE73E" : ""; // CheckMark 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MapUiTools/Avalonia/MapUiTools.Avalonia.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | AVALONIA 5 | MapControl.UiTools 6 | XAML Map Control UI Tools Library for Avalonia UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /MapUiTools/Avalonia/MenuButton.Avalonia.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Media; 4 | using Avalonia.Metadata; 5 | using Avalonia.Styling; 6 | using System; 7 | 8 | namespace MapControl.UiTools 9 | { 10 | public partial class MenuButton 11 | { 12 | public MenuButton() 13 | { 14 | var style = new Style(); 15 | style.Setters.Add(new Setter(TextBlock.FontFamilyProperty, new FontFamily("Segoe MDL2 Assets"))); 16 | style.Setters.Add(new Setter(TextBlock.FontSizeProperty, 20d)); 17 | style.Setters.Add(new Setter(PaddingProperty, new Thickness(8))); 18 | Styles.Add(style); 19 | 20 | Flyout = new MenuFlyout(); 21 | Loaded += async (s, e) => await Initialize(); 22 | } 23 | 24 | public string Icon 25 | { 26 | get => Content as string; 27 | set => Content = value; 28 | } 29 | 30 | public MenuFlyout Menu => (MenuFlyout)Flyout; 31 | 32 | [Content] 33 | public ItemCollection Items => Menu.Items; 34 | 35 | protected override Type StyleKeyOverride => typeof(Button); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MapUiTools/Shared/MapLayerMenuItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | #if WPF 5 | using System.Windows; 6 | using System.Windows.Markup; 7 | #elif UWP 8 | using Windows.UI.Xaml; 9 | using Windows.UI.Xaml.Markup; 10 | #elif WINUI 11 | using Microsoft.UI.Xaml; 12 | using Microsoft.UI.Xaml.Markup; 13 | #else 14 | using Avalonia.Metadata; 15 | using FrameworkElement = Avalonia.Controls.Control; 16 | #endif 17 | 18 | namespace MapControl.UiTools 19 | { 20 | #if WPF 21 | [ContentProperty(nameof(MapLayer))] 22 | #elif UWP || WINUI 23 | [ContentProperty(Name = nameof(MapLayer))] 24 | #endif 25 | public class MapLayerMenuItem : MapMenuItem 26 | { 27 | #if AVALONIA 28 | [Content] 29 | #endif 30 | public virtual FrameworkElement MapLayer { get; set; } 31 | 32 | public Func> MapLayerFactory { get; set; } 33 | 34 | protected override bool GetIsChecked(MapBase map) 35 | { 36 | return map.Children.Contains(MapLayer); 37 | } 38 | 39 | public override async Task Execute(MapBase map) 40 | { 41 | var layer = MapLayer ?? (MapLayer = await MapLayerFactory.Invoke()); 42 | 43 | if (layer != null) 44 | { 45 | map.MapLayer = layer; 46 | IsChecked = true; 47 | } 48 | } 49 | } 50 | 51 | public class MapOverlayMenuItem : MapLayerMenuItem 52 | { 53 | public override async Task Execute(MapBase map) 54 | { 55 | var layer = MapLayer ?? (MapLayer = await MapLayerFactory.Invoke()); 56 | 57 | if (layer != null) 58 | { 59 | if (map.Children.Contains(layer)) 60 | { 61 | map.Children.Remove(layer); 62 | } 63 | else 64 | { 65 | var index = 1; 66 | 67 | foreach (var itemLayer in ParentMenuItems 68 | .OfType() 69 | .Select(item => item.MapLayer) 70 | .Where(itemLayer => itemLayer != null)) 71 | { 72 | if (itemLayer == layer) 73 | { 74 | map.Children.Insert(index, itemLayer); 75 | break; 76 | } 77 | 78 | if (map.Children.Contains(itemLayer)) 79 | { 80 | index++; 81 | } 82 | } 83 | } 84 | 85 | IsChecked = true; 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /MapUiTools/Shared/MapProjectionMenuItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | #if WPF 5 | using System.Windows.Markup; 6 | #elif UWP 7 | using Windows.UI.Xaml.Markup; 8 | #elif WINUI 9 | using Microsoft.UI.Xaml.Markup; 10 | #else 11 | using Avalonia.Metadata; 12 | #endif 13 | 14 | namespace MapControl.UiTools 15 | { 16 | #if WPF 17 | [ContentProperty(nameof(MapProjection))] 18 | #elif UWP || WINUI 19 | [ContentProperty(Name = nameof(MapProjection))] 20 | #endif 21 | public class MapProjectionMenuItem : MapMenuItem 22 | { 23 | #if AVALONIA 24 | [Content] 25 | #endif 26 | public string MapProjection { get; set; } 27 | 28 | protected override bool GetIsChecked(MapBase map) 29 | { 30 | return map.MapProjection.CrsId == MapProjection; 31 | } 32 | 33 | public override Task Execute(MapBase map) 34 | { 35 | bool success = true; 36 | 37 | if (map.MapProjection.CrsId != MapProjection) 38 | { 39 | try 40 | { 41 | map.MapProjection = MapProjectionFactory.Instance.GetProjection(MapProjection); 42 | } 43 | catch (Exception ex) 44 | { 45 | Debug.WriteLine($"{nameof(MapProjectionFactory)}: {ex.Message}"); 46 | success = false; 47 | } 48 | } 49 | 50 | IsChecked = success; 51 | 52 | return Task.CompletedTask; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /MapUiTools/Shared/MenuButton.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | #if WPF 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | #elif UWP 6 | using Windows.UI.Xaml; 7 | using Windows.UI.Xaml.Controls; 8 | #elif WINUI 9 | using Microsoft.UI.Xaml; 10 | using Microsoft.UI.Xaml.Controls; 11 | #else 12 | using Avalonia.Controls; 13 | using DependencyProperty = Avalonia.AvaloniaProperty; 14 | #endif 15 | 16 | namespace MapControl.UiTools 17 | { 18 | public partial class MenuButton : Button 19 | { 20 | public static readonly DependencyProperty MapProperty = 21 | DependencyPropertyHelper.Register(nameof(Map), null, 22 | async (button, oldValue, newValue) => await button.Initialize()); 23 | 24 | public MapBase Map 25 | { 26 | get => (MapBase)GetValue(MapProperty); 27 | set => SetValue(MapProperty, value); 28 | } 29 | 30 | private async Task Initialize() 31 | { 32 | if (Map != null) 33 | { 34 | DataContext = Map; 35 | 36 | if (Items.Count > 0 && Items[0] is MapMenuItem item) 37 | { 38 | await item.Execute(Map); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MapUiTools/UWP/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("XAML Map Control UI Tools Library for UWP")] 5 | [assembly: AssemblyProduct("XAML Map Control")] 6 | [assembly: AssemblyCompany("Clemens Fischer")] 7 | [assembly: AssemblyCopyright("Copyright © 2024 Clemens Fischer")] 8 | [assembly: AssemblyTrademark("")] 9 | [assembly: AssemblyVersion("13.0.0")] 10 | [assembly: AssemblyFileVersion("13.0.0")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCulture("")] 13 | [assembly: ComVisible(false)] 14 | -------------------------------------------------------------------------------- /MapUiTools/UWP/Properties/MapUiTools.UWP.rd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MapUiTools/WPF/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] 4 | -------------------------------------------------------------------------------- /MapUiTools/WPF/MapMenuItem.WPF.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using System.Windows.Controls; 5 | 6 | namespace MapControl.UiTools 7 | { 8 | public abstract class MapMenuItem : MenuItem 9 | { 10 | protected MapMenuItem() 11 | { 12 | Loaded += (s, e) => 13 | { 14 | if (DataContext is MapBase map) 15 | { 16 | IsChecked = GetIsChecked(map); 17 | } 18 | }; 19 | 20 | Click += async (s, e) => 21 | { 22 | if (DataContext is MapBase map) 23 | { 24 | await Execute(map); 25 | 26 | foreach (var item in ParentMenuItems) 27 | { 28 | item.IsChecked = item.GetIsChecked(map); 29 | } 30 | } 31 | }; 32 | } 33 | 34 | public string Text 35 | { 36 | get => Header as string; 37 | set => Header = value; 38 | } 39 | 40 | protected IEnumerable ParentMenuItems => ((ItemsControl)Parent).Items.OfType(); 41 | 42 | protected abstract bool GetIsChecked(MapBase map); 43 | 44 | public abstract Task Execute(MapBase map); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MapUiTools/WPF/MapUiTools.WPF.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0-windows;net462 4 | true 5 | WPF 6 | MapControl.UiTools 7 | XAML Map Control UI Tools Library for WPF 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /MapUiTools/WPF/MenuButton.WPF.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Markup; 4 | 5 | namespace MapControl.UiTools 6 | { 7 | [ContentProperty(nameof(Items))] 8 | public partial class MenuButton 9 | { 10 | static MenuButton() 11 | { 12 | DefaultStyleKeyProperty.OverrideMetadata(typeof(MenuButton), new FrameworkPropertyMetadata(typeof(MenuButton))); 13 | } 14 | 15 | public MenuButton() 16 | { 17 | ContextMenu = new ContextMenu(); 18 | DataContextChanged += (s, e) => ContextMenu.DataContext = e.NewValue; 19 | Loaded += async (s, e) => await Initialize(); 20 | Click += (s, e) => ContextMenu.IsOpen = true; 21 | } 22 | 23 | public string Icon 24 | { 25 | get => Content as string; 26 | set => Content = value; 27 | } 28 | 29 | public ContextMenu Menu => ContextMenu; 30 | 31 | public ItemCollection Items => ContextMenu.Items; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MapUiTools/WPF/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /MapUiTools/WinUI/MapMenuItem.WinUI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | #if UWP 5 | using Windows.UI.Xaml.Controls; 6 | using Windows.UI.Xaml.Media; 7 | #else 8 | using Microsoft.UI.Xaml.Controls; 9 | using Microsoft.UI.Xaml.Media; 10 | #endif 11 | 12 | namespace MapControl.UiTools 13 | { 14 | public abstract class MapMenuItem : ToggleMenuFlyoutItem 15 | { 16 | protected MapMenuItem() 17 | { 18 | Loaded += (s, e) => 19 | { 20 | ParentMenuItems = ((Panel)VisualTreeHelper.GetParent(this)).Children.OfType().ToList(); 21 | 22 | if (DataContext is MapBase map) 23 | { 24 | IsChecked = GetIsChecked(map); 25 | } 26 | }; 27 | 28 | Click += async (s, e) => 29 | { 30 | if (DataContext is MapBase map) 31 | { 32 | await Execute(map); 33 | 34 | foreach (var item in ParentMenuItems) 35 | { 36 | item.IsChecked = item.GetIsChecked(map); 37 | } 38 | } 39 | }; 40 | } 41 | 42 | protected IList ParentMenuItems { get; private set; } 43 | 44 | protected abstract bool GetIsChecked(MapBase map); 45 | 46 | public abstract Task Execute(MapBase map); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MapUiTools/WinUI/MapUiTools.WinUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows10.0.17763.0 4 | win-x86;win-x64;win-arm64 5 | true 6 | WINUI 7 | MapControl.UiTools 8 | XAML Map Control UI Tools Library for WinUI 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /MapUiTools/WinUI/MenuButton.WinUI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | #if UWP 3 | using Windows.UI.Xaml.Controls; 4 | using Windows.UI.Xaml.Markup; 5 | #elif WINUI 6 | using Microsoft.UI.Xaml.Controls; 7 | using Microsoft.UI.Xaml.Markup; 8 | #endif 9 | 10 | namespace MapControl.UiTools 11 | { 12 | [ContentProperty(Name = nameof(Items))] 13 | public partial class MenuButton 14 | { 15 | public MenuButton() 16 | { 17 | Flyout = new MenuFlyout(); 18 | Loaded += async (s, e) => await Initialize(); 19 | } 20 | 21 | public string Icon 22 | { 23 | get => (Content as FontIcon)?.Glyph; 24 | set => Content = new FontIcon { Glyph = value }; 25 | } 26 | 27 | public MenuFlyout Menu => (MenuFlyout)Flyout; 28 | 29 | public IList Items => Menu.Items; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SampleApps/AvaloniaApp/App.axaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SampleApps/AvaloniaApp/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | 5 | namespace SampleApplication 6 | { 7 | public partial class App : Application 8 | { 9 | public override void Initialize() 10 | { 11 | AvaloniaXamlLoader.Load(this); 12 | } 13 | 14 | public override void OnFrameworkInitializationCompleted() 15 | { 16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 17 | { 18 | desktop.MainWindow = new MainWindow 19 | { 20 | DataContext = new MapViewModel() 21 | }; 22 | } 23 | 24 | base.OnFrameworkInitializationCompleted(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /SampleApps/AvaloniaApp/AvaloniaApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net9.0 5 | SampleApplication 6 | AVALONIA 7 | 8 | 9 | 10 | 11 | Never 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Always 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /SampleApps/AvaloniaApp/Program.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using System; 3 | 4 | namespace SampleApplication 5 | { 6 | class Program 7 | { 8 | [STAThread] 9 | public static void Main(string[] args) 10 | { 11 | BuildAvaloniaApp() 12 | .StartWithClassicDesktopLifetime(args); 13 | } 14 | 15 | public static AppBuilder BuildAvaloniaApp() 16 | { 17 | return AppBuilder 18 | .Configure() 19 | .UsePlatformDetect() 20 | .WithInterFont() 21 | .LogToTrace(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SampleApps/ProjectionDemo/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /SampleApps/ProjectionDemo/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace ProjectionDemo 4 | { 5 | public partial class App : Application 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /SampleApps/ProjectionDemo/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 11 | 12 | 14 | 15 | 16 | 20 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SampleApps/ProjectionDemo/ProjectionDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net9.0-windows 5 | true 6 | ProjectionDemo 7 | XAML Map Control Projection Demo Application 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /SampleApps/ProjectionDemo/ProjectionDemo.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Designer 7 | 8 | 9 | 10 | 11 | Designer 12 | 13 | 14 | -------------------------------------------------------------------------------- /SampleApps/Shared/10_535_330.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/Shared/10_535_330.jpg -------------------------------------------------------------------------------- /SampleApps/Shared/ValueConverters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | #if WINUI 5 | using Microsoft.UI.Xaml.Data; 6 | #elif UWP 7 | using Windows.UI.Xaml.Data; 8 | #elif AVALONIA 9 | using Avalonia.Data.Converters; 10 | #endif 11 | 12 | namespace SampleApplication 13 | { 14 | public class DoubleTriggerConverter : IValueConverter 15 | { 16 | public double Trigger { get; set; } 17 | public object TriggerValue { get; set; } 18 | public object DefaultValue { get; set; } 19 | 20 | public object Convert(object value, Type targetType, object parameter, string language) 21 | { 22 | var converter = TypeDescriptor.GetConverter(targetType); 23 | 24 | return (double)value == Trigger ? converter.ConvertFrom(TriggerValue) : converter.ConvertFrom(DefaultValue); 25 | } 26 | 27 | public object ConvertBack(object value, Type targetType, object parameter, string language) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 33 | { 34 | return Convert(value, targetType, parameter, ""); 35 | } 36 | 37 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 38 | { 39 | return ConvertBack(value, targetType, parameter, ""); 40 | } 41 | } 42 | 43 | public class MapHeadingToVisibilityConverter : IValueConverter 44 | { 45 | public object Convert(object value, Type targetType, object parameter, string language) 46 | { 47 | return (double)value != 0d; 48 | } 49 | 50 | public object ConvertBack(object value, Type targetType, object parameter, string language) 51 | { 52 | throw new NotImplementedException(); 53 | } 54 | 55 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 56 | { 57 | return Convert(value, targetType, parameter, ""); 58 | } 59 | 60 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 61 | { 62 | return ConvertBack(value, targetType, parameter, ""); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /SampleApps/Shared/etna.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ground Overlays 5 | Examples of ground overlays 6 | 7 | Large-scale overlay on terrain 8 | Overlay shows Mount Etna erupting on July 13th, 2001. 9 | 10 | https://developers.google.com/kml/documentation/images/etna.jpg 11 | 12 | 13 | 37.91904192681665 14 | 37.46543388598137 15 | 15.35832653742206 16 | 14.60128369746704 17 | -0.1556640799496235 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SampleApps/UniversalApp/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | -------------------------------------------------------------------------------- /SampleApps/UniversalApp/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Windows.ApplicationModel; 3 | using Windows.ApplicationModel.Activation; 4 | using Windows.UI.Xaml; 5 | using Windows.UI.Xaml.Controls; 6 | using Windows.UI.Xaml.Navigation; 7 | 8 | namespace SampleApplication 9 | { 10 | sealed partial class App : Application 11 | { 12 | public App() 13 | { 14 | InitializeComponent(); 15 | Suspending += OnSuspending; 16 | } 17 | 18 | protected override void OnLaunched(LaunchActivatedEventArgs e) 19 | { 20 | Frame rootFrame = Window.Current.Content as Frame; 21 | 22 | if (rootFrame == null) 23 | { 24 | rootFrame = new Frame(); 25 | rootFrame.NavigationFailed += OnNavigationFailed; 26 | 27 | Window.Current.Content = rootFrame; 28 | } 29 | 30 | if (rootFrame.Content == null) 31 | { 32 | rootFrame.Navigate(typeof(MainPage), e.Arguments); 33 | } 34 | 35 | Window.Current.Activate(); 36 | } 37 | 38 | private void OnNavigationFailed(object sender, NavigationFailedEventArgs e) 39 | { 40 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName); 41 | } 42 | 43 | private void OnSuspending(object sender, SuspendingEventArgs e) 44 | { 45 | var deferral = e.SuspendingOperation.GetDeferral(); 46 | deferral.Complete(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/UniversalApp/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/UniversalApp/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/UniversalApp/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/UniversalApp/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/UniversalApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/UniversalApp/Assets/StoreLogo.png -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/UniversalApp/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | UniversalApp 7 | Clemens 8 | Assets\StoreLogo.png 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("XAML Map Control UWP Sample Application")] 5 | [assembly: AssemblyProduct("XAML Map Control")] 6 | [assembly: AssemblyCompany("Clemens Fischer")] 7 | [assembly: AssemblyCopyright("Copyright © 2024 Clemens Fischer")] 8 | [assembly: AssemblyTrademark("")] 9 | [assembly: AssemblyVersion("13.0.0")] 10 | [assembly: AssemblyFileVersion("13.0.0")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCulture("")] 13 | [assembly: ComVisible(false)] 14 | -------------------------------------------------------------------------------- /SampleApps/UniversalApp/Properties/Default.rd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /SampleApps/UniversalApp/UniversalApp_TemporaryKey.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/UniversalApp/UniversalApp_TemporaryKey.pfx -------------------------------------------------------------------------------- /SampleApps/WinUiApp/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /SampleApps/WinUiApp/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | 3 | namespace SampleApplication 4 | { 5 | public partial class App : Application 6 | { 7 | private Window window; 8 | 9 | public App() 10 | { 11 | InitializeComponent(); 12 | } 13 | 14 | protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) 15 | { 16 | window = new MainWindow(); 17 | window.Activate(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/WinUiApp/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/WinUiApp/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/WinUiApp/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/WinUiApp/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/WinUiApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/WinUiApp/Assets/StoreLogo.png -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClemensFischer/XAML-Map-Control/cceb1224868fa78b5c18d7bbed68c36caa996894/SampleApps/WinUiApp/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 8 | 9 | 13 | 14 | 15 | WinUiApp 16 | Clemens 17 | Assets\StoreLogo.png 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Properties/PublishProfiles/win-arm64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | ARM64 9 | win-arm64 10 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ 11 | true 12 | False 13 | 14 | -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Properties/PublishProfiles/win-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | x64 9 | win-x64 10 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ 11 | true 12 | False 13 | 14 | -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Properties/PublishProfiles/win-x86.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | x86 9 | win-x86 10 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ 11 | true 12 | False 13 | 14 | -------------------------------------------------------------------------------- /SampleApps/WinUiApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "WinUiApp (Package)": { 4 | "commandName": "MsixPackage" 5 | }, 6 | "WinUiApp (Unpackaged)": { 7 | "commandName": "Project" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /SampleApps/WinUiApp/WinUiApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net9.0-windows10.0.17763.0 5 | x86;x64;ARM64 6 | win-x86;win-x64;win-arm64 7 | win-$(Platform).pubxml 8 | true 9 | app.manifest 10 | SampleApplication 11 | XAML Map Control WinUI Sample Application 12 | none 13 | true 14 | WINUI 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /SampleApps/WinUiApp/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | true/PM 12 | PerMonitorV2, PerMonitor 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SampleApps/WpfApplication/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SampleApps/WpfApplication/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace SampleApplication 4 | { 5 | public partial class App : Application 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /SampleApps/WpfApplication/WpfApplication.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net9.0-windows;net48 5 | true 6 | SampleApplication 7 | XAML Map Control WPF Sample Application 8 | WPF 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Always 22 | 23 | 24 | Always 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | --------------------------------------------------------------------------------