├── .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