diff --git a/SampleApps/ProjectionDemo/App.xaml b/SampleApps/ProjectionDemo/App.xaml
new file mode 100644
index 00000000..df9195b6
--- /dev/null
+++ b/SampleApps/ProjectionDemo/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/SampleApps/ProjectionDemo/App.xaml.cs b/SampleApps/ProjectionDemo/App.xaml.cs
new file mode 100644
index 00000000..d72fe032
--- /dev/null
+++ b/SampleApps/ProjectionDemo/App.xaml.cs
@@ -0,0 +1,8 @@
+using System.Windows;
+
+namespace ProjectionDemo
+{
+ public partial class App : Application
+ {
+ }
+}
diff --git a/SampleApps/ProjectionDemo/MainWindow.xaml b/SampleApps/ProjectionDemo/MainWindow.xaml
new file mode 100644
index 00000000..67edff83
--- /dev/null
+++ b/SampleApps/ProjectionDemo/MainWindow.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SampleApps/ProjectionDemo/MainWindow.xaml.cs b/SampleApps/ProjectionDemo/MainWindow.xaml.cs
new file mode 100644
index 00000000..173623f7
--- /dev/null
+++ b/SampleApps/ProjectionDemo/MainWindow.xaml.cs
@@ -0,0 +1,156 @@
+using MapControl;
+using MapControl.Projections;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Input;
+
+namespace ProjectionDemo
+{
+ public partial class MainWindow : Window
+ {
+ private readonly HttpClient httpClient = new HttpClient();
+ private readonly ViewModel viewModel = new ViewModel();
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+
+ private async Task GetWktAsync(int epsgCode)
+ {
+ var wkt = await httpClient.GetStringAsync(string.Format("https://epsg.io/{0}.wkt", epsgCode));
+
+ if (!wkt.Contains("PARAMETER[\"latitude_of_origin\",") &&
+ !wkt.Contains("PARAMETER[\"latitude_of_center\","))
+ {
+ wkt = wkt.Replace(
+ "PARAMETER[\"central_meridian\",",
+ "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",");
+ }
+
+ return wkt;
+ }
+
+ private async void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ viewModel.Projections.Add(new MapControl.Projections.WebMercatorProjection());
+
+ viewModel.Projections.Add(new GeoApiProjection
+ {
+ WKT = await httpClient.GetStringAsync("https://epsg.io/25832.wkt") // ETRS89 / UTM zone 32N
+ });
+
+ viewModel.Layers.Add(
+ "OpenStreetMap WMS",
+ new WmsImageLayer
+ {
+ ServiceUri = new Uri("http://ows.terrestris.de/osm/service"),
+ Layers = "OSM-WMS"
+ });
+
+ viewModel.Layers.Add(
+ "TopPlusOpen WMS",
+ new WmsImageLayer
+ {
+ ServiceUri = new Uri("https://sgx.geodatenzentrum.de/wms_topplus_open"),
+ Layers = "web"
+ });
+
+ viewModel.Layers.Add(
+ "Orthophotos Wiesbaden",
+ new WmsImageLayer
+ {
+ ServiceUri = new Uri("https://geoportal.wiesbaden.de/cgi-bin/mapserv.fcgi?map=d:/openwimap/umn/map/orthophoto/orthophotos.map"),
+ Layers = "orthophoto2017"
+ });
+
+ viewModel.CurrentProjection = viewModel.Projections[0];
+ viewModel.CurrentLayer = viewModel.Layers.First().Value;
+
+ DataContext = viewModel;
+ }
+
+ private void Map_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ viewModel.PushpinLocation = viewModel.CurrentProjection.ViewportPointToLocation(e.GetPosition((IInputElement)sender));
+ }
+ }
+
+ public class ViewModel : INotifyPropertyChanged
+ {
+ private MapProjection currentProjection;
+ private IMapLayer currentLayer;
+ private Location pushpinLocation = new Location();
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public List Projections { get; } = new List();
+
+ public Dictionary Layers { get; } = new Dictionary();
+
+ public MapProjection CurrentProjection
+ {
+ get => currentProjection;
+ set
+ {
+ currentProjection = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentProjection)));
+ }
+ }
+
+ public IMapLayer CurrentLayer
+ {
+ get => currentLayer;
+ set
+ {
+ currentLayer = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentLayer)));
+ }
+ }
+
+ public Location PushpinLocation
+ {
+ get => pushpinLocation;
+ set
+ {
+ pushpinLocation = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PushpinLocation)));
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PushpinText)));
+ }
+ }
+
+ public string PushpinText
+ {
+ get
+ {
+ var latitude = (int)Math.Round(PushpinLocation.Latitude * 3600);
+ var longitude = (int)Math.Round(Location.NormalizeLongitude(PushpinLocation.Longitude) * 3600);
+ var latHemisphere = 'N';
+ var lonHemisphere = 'E';
+
+ if (latitude < 0)
+ {
+ latitude = -latitude;
+ latHemisphere = 'S';
+ }
+
+ if (longitude < 0)
+ {
+ longitude = -longitude;
+ lonHemisphere = 'W';
+ }
+
+ return string.Format(CultureInfo.InvariantCulture,
+ "{0} {1:00} {2:00} {3:00}\n{4} {5:000} {6:00} {7:00}",
+ latHemisphere, latitude / 3600, (latitude / 60) % 60, latitude % 60,
+ lonHemisphere, longitude / 3600, (longitude / 60) % 60, longitude % 60);
+ }
+ }
+ }
+}
diff --git a/SampleApps/ProjectionDemo/ProjectionDemo.csproj b/SampleApps/ProjectionDemo/ProjectionDemo.csproj
new file mode 100644
index 00000000..11f756c9
--- /dev/null
+++ b/SampleApps/ProjectionDemo/ProjectionDemo.csproj
@@ -0,0 +1,19 @@
+
+
+
+ WinExe
+ netcoreapp3.1
+ true
+ 4.17.0
+ Clemens Fischer
+ XAML Map Control Map Projection Demo Application
+ XAML Map Control
+ Copyright © 2020 Clemens Fischer
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SampleApps/ProjectionDemo/ProjectionDemo.csproj.user b/SampleApps/ProjectionDemo/ProjectionDemo.csproj.user
new file mode 100644
index 00000000..644b0a6f
--- /dev/null
+++ b/SampleApps/ProjectionDemo/ProjectionDemo.csproj.user
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Designer
+
+
+
+
+ Designer
+
+
+
\ No newline at end of file