diff --git a/MapControl/Shared/BoundingBox.cs b/MapControl/Shared/BoundingBox.cs index a7d80897..8b98eaa9 100644 --- a/MapControl/Shared/BoundingBox.cs +++ b/MapControl/Shared/BoundingBox.cs @@ -67,7 +67,7 @@ namespace MapControl if (values.Length != 4) { - throw new FormatException("BoundingBox string must be a comma-separated list of four double values."); + throw new FormatException("BoundingBox string must be a comma-separated list of four floating point numbers."); } return new BoundingBox( diff --git a/MapControlExtended.sln b/MapControlExtended.sln index 1c8d5989..d6c33a82 100644 --- a/MapControlExtended.sln +++ b/MapControlExtended.sln @@ -64,6 +64,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBTiles.WinUI", "MBTiles\Wi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinUiApp", "SampleApps\WinUiApp\WinUiApp.csproj", "{751EF297-7CF4-4879-BA8F-42661FA68668}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectionDemo", "SampleApps\ProjectionDemo\ProjectionDemo.csproj", "{AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -428,6 +430,22 @@ Global {751EF297-7CF4-4879-BA8F-42661FA68668}.Release|x86.ActiveCfg = Release|x86 {751EF297-7CF4-4879-BA8F-42661FA68668}.Release|x86.Build.0 = Release|x86 {751EF297-7CF4-4879-BA8F-42661FA68668}.Release|x86.Deploy.0 = Release|x86 + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Debug|arm64.ActiveCfg = Debug|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Debug|arm64.Build.0 = Debug|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Debug|x64.ActiveCfg = Debug|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Debug|x64.Build.0 = Debug|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Debug|x86.ActiveCfg = Debug|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Debug|x86.Build.0 = Debug|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Release|Any CPU.Build.0 = Release|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Release|arm64.ActiveCfg = Release|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Release|arm64.Build.0 = Release|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Release|x64.ActiveCfg = Release|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Release|x64.Build.0 = Release|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Release|x86.ActiveCfg = Release|Any CPU + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -454,6 +472,7 @@ Global {1F9FBADF-65C0-453D-9B45-7A88044F807F} = {2FDC8B91-FB95-4C57-8183-63587FBFE180} {817D606F-A22D-485C-89CF-86062C8E97EF} = {CEAD0EA1-A971-4F5F-9EAE-C72F75D1F737} {751EF297-7CF4-4879-BA8F-42661FA68668} = {8F2103C2-78AF-4810-8FB9-67572F50C8FC} + {AC8C7BE0-9E72-434B-8BF3-FAEFAC2E859C} = {8F2103C2-78AF-4810-8FB9-67572F50C8FC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {458346DD-B23F-4FDC-8F9D-A10F1882A4DB} diff --git a/SampleApps/ProjectionDemo/MainWindow.xaml.cs b/SampleApps/ProjectionDemo/MainWindow.xaml.cs index 917c1d5a..3a928aed 100644 --- a/SampleApps/ProjectionDemo/MainWindow.xaml.cs +++ b/SampleApps/ProjectionDemo/MainWindow.xaml.cs @@ -6,7 +6,6 @@ using System.ComponentModel; using System.Globalization; using System.Linq; using System.Net.Http; -using System.Threading.Tasks; using System.Windows; using System.Windows.Input; diff --git a/SampleApps/ProjectionDemo/ProjectionDemo.csproj b/SampleApps/ProjectionDemo/ProjectionDemo.csproj index 2472a479..02bcc573 100644 --- a/SampleApps/ProjectionDemo/ProjectionDemo.csproj +++ b/SampleApps/ProjectionDemo/ProjectionDemo.csproj @@ -4,11 +4,11 @@ WinExe net6.0-windows true - 6.1.0 + 7.0.0 Clemens Fischer XAML Map Control Map Projection Demo Application XAML Map Control - Copyright © 2020 Clemens Fischer + Copyright © 2021 Clemens Fischer diff --git a/SampleApps/Shared/MapLayers.cs b/SampleApps/Shared/MapLayers.cs deleted file mode 100644 index 2a2ced6e..00000000 --- a/SampleApps/Shared/MapLayers.cs +++ /dev/null @@ -1,182 +0,0 @@ -using MapControl; -using System; -using System.Collections.Generic; -using System.ComponentModel; -#if WINUI -using Microsoft.UI; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Media; -#elif UWP -using Windows.UI; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media; -#else -using System.Windows; -using System.Windows.Media; -#endif - -namespace SampleApplication -{ - public class MapLayers : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - - // See https://wiki.openstreetmap.org/wiki/Tile_servers for a list of free OpenStreetMap Tile Servers - - private readonly Dictionary mapLayers = new Dictionary - { - { - "OpenStreetMap", - new MapTileLayer - { - TileSource = new TileSource { UriFormat = "https://tile.openstreetmap.org/{z}/{x}/{y}.png" }, - SourceName = "OpenStreetMap", - Description = "© [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)" - } - }, - { - "OpenStreetMap German", - new MapTileLayer - { - TileSource = new TileSource { UriFormat = "https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png" }, - SourceName = "OpenStreetMap German", - Description = "© [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)" - } - }, - { - "OpenStreetMap French", - new MapTileLayer - { - TileSource = new TileSource { UriFormat = "http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png" }, - SourceName = "OpenStreetMap French", - Description = "© [OpenStreetMap France](https://www.openstreetmap.fr/mentions-legales/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)" - } - }, - { - "OpenTopoMap", - new MapTileLayer - { - TileSource = new TileSource { UriFormat = "https://tile.opentopomap.org/{z}/{x}/{y}.png" }, - SourceName = "OpenTopoMap", - Description = "© [OpenTopoMap](https://opentopomap.org/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)", - MaxZoomLevel = 17 - } - }, - { - "Seamarks", - new MapTileLayer - { - TileSource = new TileSource { UriFormat = "http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png" }, - SourceName = "OpenSeaMap", - MinZoomLevel = 9, - MaxZoomLevel = 18 - } - }, - { - "Bing Maps Road", - new BingMapsTileLayer - { - Mode = BingMapsTileLayer.MapMode.Road, - SourceName = "Bing Maps Road", - Description = "© [Microsoft](http://www.bing.com/maps/)" - } - }, - { - "Bing Maps Aerial", - new BingMapsTileLayer - { - Mode = BingMapsTileLayer.MapMode.Aerial, - SourceName = "Bing Maps Aerial", - Description = "© [Microsoft](http://www.bing.com/maps/)", - MapForeground = new SolidColorBrush(Colors.White), - MapBackground = new SolidColorBrush(Colors.Black) - } - }, - { - "Bing Maps Aerial with Labels", - new BingMapsTileLayer - { - Mode = BingMapsTileLayer.MapMode.AerialWithLabels, - SourceName = "Bing Maps Hybrid", - Description = "© [Microsoft](http://www.bing.com/maps/)", - MapForeground = new SolidColorBrush(Colors.White), - MapBackground = new SolidColorBrush(Colors.Black) - } - }, - { - "TopPlusOpen WMTS", - new WmtsTileLayer - { - SourceName = "TopPlusOpen", - Description = "© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wmts-topplusopen-wmts-topplus-open.html)", - CapabilitiesUri = new Uri("https://sgx.geodatenzentrum.de/wmts_topplus_open/1.0.0/WMTSCapabilities.xml") - } - }, - { - "TopPlusOpen WMS", - new WmsImageLayer - { - Description = "© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wms-topplusopen-mit-layer-fur-normalausgabe-und-druck-wms-topplus-open.html)", - ServiceUri = new Uri("https://sgx.geodatenzentrum.de/wms_topplus_open") - } - }, - { - "OpenStreetMap WMS", - new WmsImageLayer - { - Description = "© [terrestris GmbH & Co. KG](http://ows.terrestris.de/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)", - ServiceUri = new Uri("http://ows.terrestris.de/osm/service") - } - }, - }; - - private string currentMapLayerName = "OpenStreetMap"; - - public string CurrentMapLayerName - { - get { return currentMapLayerName; } - set - { - currentMapLayerName = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentMapLayerName))); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentMapLayer))); - } - } - - public UIElement CurrentMapLayer - { - get { return mapLayers[currentMapLayerName]; } - } - - public UIElement SeamarksLayer - { - get { return mapLayers["Seamarks"]; } - } - - public List MapLayerNames { get; } = new List - { - "OpenStreetMap", - "OpenStreetMap German", - "OpenStreetMap French", - "OpenTopoMap", - "TopPlusOpen WMTS", - "TopPlusOpen WMS", - "OpenStreetMap WMS", - }; - - public MapLayers() - { - // Add Bing Maps TileLayers with tile URLs retrieved from the Imagery Metadata Service - // (http://msdn.microsoft.com/en-us/library/ff701716.aspx). - // A Bing Maps API Key (http://msdn.microsoft.com/en-us/library/ff428642.aspx) is required - // for using these layers and must be assigned to the static BingMapsTileLayer.ApiKey property. - - if (!string.IsNullOrEmpty(BingMapsTileLayer.ApiKey)) - { - MapLayerNames.Add("Bing Maps Road"); - MapLayerNames.Add("Bing Maps Aerial"); - MapLayerNames.Add("Bing Maps Aerial with Labels"); - } - } - } -} diff --git a/SampleApps/Shared/MapLayersMenuButton.cs b/SampleApps/Shared/MapLayersMenuButton.cs new file mode 100644 index 00000000..3716ae92 --- /dev/null +++ b/SampleApps/Shared/MapLayersMenuButton.cs @@ -0,0 +1,158 @@ +using MapControl; +using System.Collections.Generic; +using System.Linq; +#if WINUI +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +#elif UWP +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +#else +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +#endif + +namespace SampleApplication +{ + public class MapLayersMenuButton : MenuButton + { + public MapLayersMenuButton() + { +#if WINUI || UWP + Content = new FontIcon + { + FontFamily = new FontFamily("Segoe MDL2 Assets"), + Glyph = "\uE81E" + }; +#else + FontFamily = new FontFamily("Segoe MDL2 Assets"); + Content = "\uE81E"; +#endif + } + + public static readonly DependencyProperty MapProperty = DependencyProperty.Register( + nameof(Map), typeof(MapBase), typeof(MapLayersMenuButton), + new PropertyMetadata(null, (o, e) => ((MapLayersMenuButton)o).InitializeMenu())); + + public static readonly DependencyProperty MapLayersProperty = DependencyProperty.Register( + nameof(MapLayers), typeof(IDictionary), typeof(MapLayersMenuButton), + new PropertyMetadata(null, (o, e) => ((MapLayersMenuButton)o).InitializeMenu())); + + public static readonly DependencyProperty MapOverlaysProperty = DependencyProperty.Register( + nameof(MapOverlays), typeof(IDictionary), typeof(MapLayersMenuButton), + new PropertyMetadata(null, (o, e) => ((MapLayersMenuButton)o).InitializeMenu())); + + public MapBase Map + { + get { return (MapBase)GetValue(MapProperty); } + set { SetValue(MapProperty, value); } + } + + public IDictionary MapLayers + { + get { return (IDictionary)GetValue(MapLayersProperty); } + set { SetValue(MapLayersProperty, value); } + } + + public IDictionary MapOverlays + { + get { return (IDictionary)GetValue(MapOverlaysProperty); } + set { SetValue(MapOverlaysProperty, value); } + } + + private void InitializeMenu() + { + if (Map != null && MapLayers != null) + { + var menu = CreateMenu(); + + foreach (var layer in MapLayers) + { + menu.Items.Add(CreateMenuItem(layer.Key, layer.Value, MapLayerClicked)); + } + + var initialLayer = MapLayers.Values.FirstOrDefault(); + + if (MapOverlays != null && MapOverlays.Any()) + { + if (initialLayer != null) + { + menu.Items.Add(CreateSeparator()); + } + + foreach (var overlay in MapOverlays) + { + menu.Items.Add(CreateMenuItem(overlay.Key, overlay.Value, MapOverlayClicked)); + } + } + + if (initialLayer != null) + { + SetMapLayer(initialLayer); + } + } + } + + private void MapLayerClicked(object sender, RoutedEventArgs e) + { + var item = (FrameworkElement)sender; + var layer = (UIElement)item.Tag; + + SetMapLayer(layer); + } + + private void MapOverlayClicked(object sender, RoutedEventArgs e) + { + var item = (FrameworkElement)sender; + var layer = (UIElement)item.Tag; + + ToggleMapOverlay(layer); + } + + private void SetMapLayer(UIElement layer) + { + Map.MapLayer = layer; + + UpdateCheckedStates(); + } + + private void ToggleMapOverlay(UIElement layer) + { + if (Map.Children.Contains(layer)) + { + Map.Children.Remove(layer); + } + else + { + int index = 1; + + foreach (var overlay in MapOverlays.Values) + { + if (overlay == layer) + { + Map.Children.Insert(index, layer); + break; + } + + if (Map.Children.Contains(overlay)) + { + index++; + } + } + } + + UpdateCheckedStates(); + } + + private void UpdateCheckedStates() + { + foreach (var item in GetMenuItems()) + { + item.IsChecked = Map.Children.Contains((UIElement)item.Tag); + } + } + } +} diff --git a/SampleApps/Shared/MapProjectionsMenuButton.cs b/SampleApps/Shared/MapProjectionsMenuButton.cs new file mode 100644 index 00000000..dbd427a5 --- /dev/null +++ b/SampleApps/Shared/MapProjectionsMenuButton.cs @@ -0,0 +1,94 @@ +using MapControl; +using System.Collections.Generic; +using System.Linq; +#if WINUI +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +#elif UWP +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +#else +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +#endif + +namespace SampleApplication +{ + public class MapProjectionsMenuButton : MenuButton + { + public MapProjectionsMenuButton() + { +#if WINUI || UWP + Content = new FontIcon + { + FontFamily = new FontFamily("Segoe MDL2 Assets"), + Glyph = "\uE809" + }; +#else + FontFamily = new FontFamily("Segoe MDL2 Assets"); + Content = "\uE809"; +#endif + } + + public static readonly DependencyProperty MapProperty = DependencyProperty.Register( + nameof(Map), typeof(MapBase), typeof(MapProjectionsMenuButton), + new PropertyMetadata(null, (o, e) => ((MapProjectionsMenuButton)o).InitializeMenu())); + + public static readonly DependencyProperty MapProjectionsProperty = DependencyProperty.Register( + nameof(MapProjections), typeof(IDictionary), typeof(MapProjectionsMenuButton), + new PropertyMetadata(null, (o, e) => ((MapProjectionsMenuButton)o).InitializeMenu())); + + public MapBase Map + { + get { return (MapBase)GetValue(MapProperty); } + set { SetValue(MapProperty, value); } + } + + public IDictionary MapProjections + { + get { return (IDictionary)GetValue(MapProjectionsProperty); } + set { SetValue(MapProjectionsProperty, value); } + } + + private void InitializeMenu() + { + if (Map != null && MapProjections != null) + { + var menu = CreateMenu(); + + foreach (var projection in MapProjections) + { + menu.Items.Add(CreateMenuItem(projection.Key, projection.Value, MapProjectionClicked)); + } + + var initialProjection = MapProjections.Values.FirstOrDefault(); + + if (initialProjection != null) + { + SetMapProjection(initialProjection); + } + } + } + + private void MapProjectionClicked(object sender, RoutedEventArgs e) + { + var item = (FrameworkElement)sender; + var projection = (MapProjection)item.Tag; + + SetMapProjection(projection); + } + + private void SetMapProjection(MapProjection projection) + { + Map.MapProjection = projection; + + foreach (var item in GetMenuItems()) + { + item.IsChecked = Map.MapProjection == (MapProjection)item.Tag; + } + } + } +} diff --git a/SampleApps/Shared/MapViewModel.cs b/SampleApps/Shared/MapViewModel.cs index 7b87d53b..27a51042 100644 --- a/SampleApps/Shared/MapViewModel.cs +++ b/SampleApps/Shared/MapViewModel.cs @@ -1,95 +1,233 @@ using MapControl; -using System.Collections.ObjectModel; -using System.ComponentModel; +using System; +using System.Collections.Generic; +#if WINUI +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; +#elif UWP +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; +#else +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +#endif namespace SampleApplication { - public class ViewModelBase : INotifyPropertyChanged + public class PointItem { - public event PropertyChangedEventHandler PropertyChanged; + public string Name { get; set; } - protected void RaisePropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + public Location Location { get; set; } } - public class PointItem : ViewModelBase - { - private string name; - public string Name - { - get { return name; } - set - { - name = value; - RaisePropertyChanged(nameof(Name)); - } - } - - private Location location; - public Location Location - { - get { return location; } - set - { - location = value; - RaisePropertyChanged(nameof(Location)); - } - } - } - - public class Polyline + public class PolylineItem { public LocationCollection Locations { get; set; } } - public class MapViewModel : ViewModelBase + public class MapViewModel { - private Location mapCenter = new Location(53.5, 8.2); - public Location MapCenter + public List Points { get; } = new List(); + public List Pushpins { get; } = new List(); + public List Polylines { get; } = new List(); + + public Dictionary MapProjections { get; } = new Dictionary + { + { "Web Mercator", new WebMercatorProjection() }, + { "World Mercator", new WorldMercatorProjection() }, + { "Equirectangular", new EquirectangularProjection() }, + { "Orthographic", new OrthographicProjection() }, + { "Gnomonic", new GnomonicProjection() }, + { "Stereographic", new StereographicProjection() } + }; + + public Dictionary MapLayers { get; } = new Dictionary { - get { return mapCenter; } - set { - mapCenter = value; - RaisePropertyChanged(nameof(MapCenter)); + "OpenStreetMap", + new MapTileLayer + { + TileSource = new TileSource { UriFormat = "https://tile.openstreetmap.org/{z}/{x}/{y}.png" }, + SourceName = "OpenStreetMap", + Description = "© [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)" + } + }, + { + "OpenStreetMap German", + new MapTileLayer + { + TileSource = new TileSource { UriFormat = "https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png" }, + SourceName = "OpenStreetMap German", + Description = "© [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)" + } + }, + { + "OpenStreetMap French", + new MapTileLayer + { + TileSource = new TileSource { UriFormat = "http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png" }, + SourceName = "OpenStreetMap French", + Description = "© [OpenStreetMap France](https://www.openstreetmap.fr/mentions-legales/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)" + } + }, + { + "OpenTopoMap", + new MapTileLayer + { + TileSource = new TileSource { UriFormat = "https://tile.opentopomap.org/{z}/{x}/{y}.png" }, + SourceName = "OpenTopoMap", + Description = "© [OpenTopoMap](https://opentopomap.org/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)", + MaxZoomLevel = 17 + } + }, + { + "TopPlusOpen WMTS", + new WmtsTileLayer + { + SourceName = "TopPlusOpen", + Description = "© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wmts-topplusopen-wmts-topplus-open.html)", + CapabilitiesUri = new Uri("https://sgx.geodatenzentrum.de/wmts_topplus_open/1.0.0/WMTSCapabilities.xml") + } + }, + { + "TopPlusOpen WMS", + new WmsImageLayer + { + Description = "© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wms-topplusopen-mit-layer-fur-normalausgabe-und-druck-wms-topplus-open.html)", + ServiceUri = new Uri("https://sgx.geodatenzentrum.de/wms_topplus_open") + } + }, + { + "OpenStreetMap WMS", + new WmsImageLayer + { + Description = "© [terrestris GmbH & Co. KG](http://ows.terrestris.de/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)", + ServiceUri = new Uri("http://ows.terrestris.de/osm/service") + } } - } + }; - public ObservableCollection Points { get; } = new ObservableCollection(); - public ObservableCollection Pushpins { get; } = new ObservableCollection(); - public ObservableCollection Polylines { get; } = new ObservableCollection(); - - public MapLayers MapLayers { get; } = new MapLayers(); + public Dictionary MapOverlays { get; } = new Dictionary + { + { + "Sample Image", + new Image() + }, + { + "Seamarks", + new MapTileLayer + { + TileSource = new TileSource { UriFormat = "http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png" }, + SourceName = "OpenSeaMap", + MinZoomLevel = 9, + MaxZoomLevel = 18 + } + }, + { + "Graticule", + new MapGraticule + { + Opacity = 0.75 + } + }, + { + "Scale", + new MapScale + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Bottom + } + } + }; public MapViewModel() { + // Add Bing Maps TileLayers with tile URLs retrieved from the Imagery Metadata Service + // (http://msdn.microsoft.com/en-us/library/ff701716.aspx). + // A Bing Maps API Key (http://msdn.microsoft.com/en-us/library/ff428642.aspx) is required + // for using these layers and must be assigned to the static BingMapsTileLayer.ApiKey property. + + if (!string.IsNullOrEmpty(BingMapsTileLayer.ApiKey)) + { + MapLayers.Add( + "Bing Maps Road", + new BingMapsTileLayer + { + Mode = BingMapsTileLayer.MapMode.Road, + SourceName = "Bing Maps Road", + Description = "© [Microsoft](http://www.bing.com/maps/)" + }); + + MapLayers.Add( + "Bing Maps Aerial", + new BingMapsTileLayer + { + Mode = BingMapsTileLayer.MapMode.Aerial, + SourceName = "Bing Maps Aerial", + Description = "© [Microsoft](http://www.bing.com/maps/)", + MapForeground = new SolidColorBrush(Colors.White), + MapBackground = new SolidColorBrush(Colors.Black) + }); + + MapLayers.Add( + "Bing Maps Aerial with Labels", + new BingMapsTileLayer + { + Mode = BingMapsTileLayer.MapMode.AerialWithLabels, + SourceName = "Bing Maps Hybrid", + Description = "© [Microsoft](http://www.bing.com/maps/)", + MapForeground = new SolidColorBrush(Colors.White), + MapBackground = new SolidColorBrush(Colors.Black) + }); + } + + var sampleImage = (Image)MapOverlays["Sample Image"]; +#if WINUI || UWP + sampleImage.Source = new BitmapImage(new Uri("ms-appx:///10_535_330.jpg")); +#else + sampleImage.Source = new BitmapImage(new Uri("pack://siteoforigin:,,,/10_535_330.jpg")); +#endif + MapPanel.SetBoundingBox(sampleImage, new BoundingBox(53.54031, 8.08594, 53.74871, 8.43750)); + Points.Add(new PointItem { Name = "Steinbake Leitdamm", Location = new Location(53.51217, 8.16603) }); + Points.Add(new PointItem { Name = "Buhne 2", Location = new Location(53.50926, 8.15815) }); + Points.Add(new PointItem { Name = "Buhne 4", Location = new Location(53.50468, 8.15343) }); + Points.Add(new PointItem { Name = "Buhne 6", Location = new Location(53.50092, 8.15267) }); + Points.Add(new PointItem { Name = "Buhne 8", Location = new Location(53.49871, 8.15321) }); + Points.Add(new PointItem { Name = "Buhne 10", @@ -101,27 +239,31 @@ namespace SampleApplication Name = "WHV - Eckwarderhörne", Location = new Location(53.5495, 8.1877) }); + Pushpins.Add(new PointItem { Name = "JadeWeserPort", Location = new Location(53.5914, 8.14) }); + Pushpins.Add(new PointItem { Name = "Kurhaus Dangast", Location = new Location(53.447, 8.1114) }); + Pushpins.Add(new PointItem { Name = "Eckwarderhörne", Location = new Location(53.5207, 8.2323) }); - Polylines.Add(new Polyline + Polylines.Add(new PolylineItem { Locations = LocationCollection.Parse("53.5140,8.1451 53.5123,8.1506 53.5156,8.1623 53.5276,8.1757 53.5491,8.1852 53.5495,8.1877 53.5426,8.1993 53.5184,8.2219 53.5182,8.2386 53.5195,8.2387") }); - Polylines.Add(new Polyline + + Polylines.Add(new PolylineItem { Locations = LocationCollection.Parse("53.5978,8.1212 53.6018,8.1494 53.5859,8.1554 53.5852,8.1531 53.5841,8.1539 53.5802,8.1392 53.5826,8.1309 53.5867,8.1317 53.5978,8.1212") }); diff --git a/SampleApps/Shared/MenuButton.cs b/SampleApps/Shared/MenuButton.cs new file mode 100644 index 00000000..02f8d4ee --- /dev/null +++ b/SampleApps/Shared/MenuButton.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Linq; +#if WINUI +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +#elif UWP +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +#else +using System.Windows; +using System.Windows.Controls; +#endif + +namespace SampleApplication +{ + public class MenuButton : Button + { +#if WINUI || UWP + protected MenuFlyout CreateMenu() + { + var menu = new MenuFlyout(); + Flyout = menu; + return menu; + } + + protected IEnumerable GetMenuItems() + { + return ((MenuFlyout)Flyout).Items.OfType(); + } + + protected static ToggleMenuFlyoutItem CreateMenuItem(string text, object item, RoutedEventHandler click) + { + var menuItem = new ToggleMenuFlyoutItem { Text = text, Tag = item }; + menuItem.Click += click; + return menuItem; + } + + protected static MenuFlyoutSeparator CreateSeparator() + { + return new MenuFlyoutSeparator(); + } +#else + protected ContextMenu CreateMenu() + { + var menu = new ContextMenu(); + ContextMenu = menu; + return menu; + } + + protected IEnumerable GetMenuItems() + { + return ContextMenu.Items.OfType(); + } + + protected static MenuItem CreateMenuItem(string text, object item, RoutedEventHandler click) + { + var menuItem = new MenuItem { Header = text, Tag = item }; + menuItem.Click += click; + return menuItem; + } + + protected static Separator CreateSeparator() + { + return new Separator(); + } + + protected MenuButton() + { + Click += (s, e) => ContextMenu.IsOpen = true; + } +#endif + } +} diff --git a/SampleApps/UniversalApp/MainPage.xaml b/SampleApps/UniversalApp/MainPage.xaml index a7ce5289..fa58916a 100644 --- a/SampleApps/UniversalApp/MainPage.xaml +++ b/SampleApps/UniversalApp/MainPage.xaml @@ -2,129 +2,99 @@ x:Class="SampleApplication.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:SampleApplication" - xmlns:map="using:MapControl"> + xmlns:map="using:MapControl" + xmlns:local="using:SampleApplication"> - - - - - - - - - - - - - - - - - - + + + - - - - + + + + - + - - - - - + + - + + + + - - - - + local:HyperlinkText.InlinesSource="{Binding MapLayer.Description, ElementName=map}"/> - - - - - - - - - - + + - - - - + - - - - + + - - - - - - - - - - - - - - - - + + + diff --git a/SampleApps/UniversalApp/MainPage.xaml.cs b/SampleApps/UniversalApp/MainPage.xaml.cs index 20dd9155..6a96af14 100644 --- a/SampleApps/UniversalApp/MainPage.xaml.cs +++ b/SampleApps/UniversalApp/MainPage.xaml.cs @@ -1,10 +1,11 @@ -using System; +using MapControl; +using System; using System.Diagnostics; +using System.Globalization; using System.IO; -using MapControl; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Input; namespace SampleApplication { @@ -28,30 +29,42 @@ namespace SampleApplication } } - public MapViewModel ViewModel { get; } = new MapViewModel(); - public MainPage() { InitializeComponent(); - DataContext = ViewModel; } - private void ImageOpacitySliderValueChanged(object sender, RangeBaseValueChangedEventArgs e) + private void MapPointerMoved(object sender, PointerRoutedEventArgs e) { - if (mapImage != null) + var location = map.ViewToLocation(e.GetCurrentPoint(map).Position); + var latitude = (int)Math.Round(location.Latitude * 60000d); + var longitude = (int)Math.Round(Location.NormalizeLongitude(location.Longitude) * 60000d); + var latHemisphere = 'N'; + var lonHemisphere = 'E'; + + if (latitude < 0) { - mapImage.Opacity = e.NewValue / 100; + latitude = -latitude; + latHemisphere = 'S'; } + + if (longitude < 0) + { + longitude = -longitude; + lonHemisphere = 'W'; + } + + mouseLocation.Text = string.Format(CultureInfo.InvariantCulture, + "{0} {1:00} {2:00.000}\n{3} {4:000} {5:00.000}", + latHemisphere, latitude / 60000, (latitude % 60000) / 1000d, + lonHemisphere, longitude / 60000, (longitude % 60000) / 1000d); + mouseLocation.Visibility = Visibility.Visible; } - private void SeamarksChecked(object sender, RoutedEventArgs e) + private void MapPointerExited(object sender, PointerRoutedEventArgs e) { - map.Children.Insert(map.Children.IndexOf(graticule), ViewModel.MapLayers.SeamarksLayer); - } - - private void SeamarksUnchecked(object sender, RoutedEventArgs e) - { - map.Children.Remove(ViewModel.MapLayers.SeamarksLayer); + mouseLocation.Visibility = Visibility.Collapsed; + mouseLocation.Text = string.Empty; } } } diff --git a/SampleApps/UniversalApp/Package.appxmanifest b/SampleApps/UniversalApp/Package.appxmanifest index b28221bc..0bfe9e19 100644 --- a/SampleApps/UniversalApp/Package.appxmanifest +++ b/SampleApps/UniversalApp/Package.appxmanifest @@ -14,8 +14,8 @@ - - + + diff --git a/SampleApps/UniversalApp/UniversalApp.csproj b/SampleApps/UniversalApp/UniversalApp.csproj index b5a1d07b..dea63180 100644 --- a/SampleApps/UniversalApp/UniversalApp.csproj +++ b/SampleApps/UniversalApp/UniversalApp.csproj @@ -7,7 +7,7 @@ {AA62B4AA-1CA3-4C20-BEB7-B824D0FC4BD1} AppContainerExe Properties - UniversalApp + SampleApplication UniversalApp en-US UAP @@ -51,12 +51,18 @@ HyperlinkText.cs - - MapLayers.cs + + MapLayersMenuButton.cs + + + MapProjectionsMenuButton.cs MapViewModel.cs + + MenuButton.cs + App.xaml diff --git a/SampleApps/WinUiApp/MainWindow.xaml b/SampleApps/WinUiApp/MainWindow.xaml index e5774456..c0acaaac 100644 --- a/SampleApps/WinUiApp/MainWindow.xaml +++ b/SampleApps/WinUiApp/MainWindow.xaml @@ -2,24 +2,15 @@ x:Class="SampleApplication.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:map="using:MapControl"> + xmlns:map="using:MapControl" + xmlns:local="using:SampleApplication"> - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + - + - - - - - - - - - - - + + - - - - + - - - - + + - - - - - - - - - - - - - - - - + diff --git a/SampleApps/WpfApplication/MainWindow.xaml.cs b/SampleApps/WpfApplication/MainWindow.xaml.cs index 0677b3d5..78df6f32 100644 --- a/SampleApps/WpfApplication/MainWindow.xaml.cs +++ b/SampleApps/WpfApplication/MainWindow.xaml.cs @@ -1,12 +1,12 @@ -using System; +using MapControl; +using MapControl.Caching; +using System; using System.Diagnostics; using System.Globalization; using System.IO; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; -using MapControl; -using MapControl.Caching; namespace SampleApplication { @@ -108,15 +108,5 @@ namespace SampleApplication mapItem.IsSelected = !mapItem.IsSelected; e.Handled = true; } - - private void SeamarksChecked(object sender, RoutedEventArgs e) - { - map.Children.Insert(map.Children.IndexOf(graticule), ((MapViewModel)DataContext).MapLayers.SeamarksLayer); - } - - private void SeamarksUnchecked(object sender, RoutedEventArgs e) - { - map.Children.Remove(((MapViewModel)DataContext).MapLayers.SeamarksLayer); - } } } diff --git a/SampleApps/WpfApplication/OutlinedText.cs b/SampleApps/WpfApplication/OutlinedText.cs index 238243a4..69484090 100644 --- a/SampleApps/WpfApplication/OutlinedText.cs +++ b/SampleApps/WpfApplication/OutlinedText.cs @@ -1,4 +1,5 @@ -using System.Windows; +using System.Globalization; +using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -6,36 +7,49 @@ namespace SampleApplication { public class OutlinedText : FrameworkElement { - private GlyphRun glyphRun; + private FormattedText text; private Geometry outline; public static readonly DependencyProperty TextProperty = TextBlock.TextProperty.AddOwner( - typeof(OutlinedText), new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).glyphRun = null) { AffectsMeasure = true }); + typeof(OutlinedText), + new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).text = null) { AffectsMeasure = true }); public static readonly DependencyProperty FontSizeProperty = TextBlock.FontSizeProperty.AddOwner( - typeof(OutlinedText), new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).glyphRun = null) { AffectsMeasure = true }); + typeof(OutlinedText), + new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).text = null) { AffectsMeasure = true }); public static readonly DependencyProperty FontFamilyProperty = TextBlock.FontFamilyProperty.AddOwner( - typeof(OutlinedText), new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).glyphRun = null) { AffectsMeasure = true }); + typeof(OutlinedText), + new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).text = null) { AffectsMeasure = true }); public static readonly DependencyProperty FontStyleProperty = TextBlock.FontStyleProperty.AddOwner( - typeof(OutlinedText), new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).glyphRun = null) { AffectsMeasure = true }); + typeof(OutlinedText), + new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).text = null) { AffectsMeasure = true }); public static readonly DependencyProperty FontWeightProperty = TextBlock.FontWeightProperty.AddOwner( - typeof(OutlinedText), new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).glyphRun = null) { AffectsMeasure = true }); + typeof(OutlinedText), + new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).text = null) { AffectsMeasure = true }); public static readonly DependencyProperty FontStretchProperty = TextBlock.FontStretchProperty.AddOwner( - typeof(OutlinedText), new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).glyphRun = null) { AffectsMeasure = true }); + typeof(OutlinedText), + new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).text = null) { AffectsMeasure = true }); public static readonly DependencyProperty ForegroundProperty = TextBlock.ForegroundProperty.AddOwner( - typeof(OutlinedText), new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).glyphRun = null) { AffectsMeasure = true }); + typeof(OutlinedText), + new FrameworkPropertyMetadata((o, e) => ((OutlinedText)o).text = null) { AffectsMeasure = true }); public static readonly DependencyProperty BackgroundProperty = TextBlock.BackgroundProperty.AddOwner( - typeof(OutlinedText), new FrameworkPropertyMetadata(Brushes.White, (o, e) => ((OutlinedText)o).glyphRun = null) { AffectsMeasure = true }); + typeof(OutlinedText), + new FrameworkPropertyMetadata(Brushes.White, (o, e) => ((OutlinedText)o).text = null) { AffectsMeasure = true }); public static readonly DependencyProperty OutlineThicknessProperty = DependencyProperty.Register( - "OutlineThickness", typeof(double), typeof(OutlinedText), - new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsMeasure, (o, e) => ((OutlinedText)o).glyphRun = null)); + nameof(OutlineThickness), typeof(double), typeof(OutlinedText), + new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsMeasure, (o, e) => ((OutlinedText)o).text = null)); + + public OutlinedText() + { + IsHitTestVisible = false; + } public string Text { @@ -93,23 +107,23 @@ namespace SampleApplication protected override Size MeasureOverride(Size availableSize) { - return CheckGlyphRun() ? outline.Bounds.Size : new Size(); + return ValidateText() ? outline.Bounds.Size : new Size(); } protected override void OnRender(DrawingContext drawingContext) { - if (CheckGlyphRun()) + if (ValidateText()) { var location = outline.Bounds.Location; drawingContext.PushTransform(new TranslateTransform(-location.X, -location.Y)); drawingContext.DrawGeometry(Background, null, outline); - drawingContext.DrawGlyphRun(Foreground, glyphRun); + drawingContext.DrawText(text, new Point()); } } - private bool CheckGlyphRun() + private bool ValidateText() { - if (glyphRun == null) + if (text == null) { if (string.IsNullOrEmpty(Text)) { @@ -123,19 +137,22 @@ namespace SampleApplication return false; } - var glyphIndices = new ushort[Text.Length]; - var advanceWidths = new double[Text.Length]; + text = new FormattedText(Text, + CultureInfo.InvariantCulture, + FlowDirection.LeftToRight, + typeface, + FontSize, + Foreground, + VisualTreeHelper.GetDpi(this).PixelsPerDip); - for (int i = 0; i < Text.Length; i++) - { - var glyphIndex = glyphTypeface.CharacterToGlyphMap[Text[i]]; - glyphIndices[i] = glyphIndex; - advanceWidths[i] = glyphTypeface.AdvanceWidths[glyphIndex] * FontSize; - } - - glyphRun = new GlyphRun(glyphTypeface, 0, false, FontSize, 1f, glyphIndices, new Point(), advanceWidths, null, null, null, null, null, null); - - outline = glyphRun.BuildGeometry().GetWidenedPathGeometry(new Pen(null, OutlineThickness * 2d)); + outline = text.BuildGeometry(new Point()).GetWidenedPathGeometry( + new Pen + { + Thickness = OutlineThickness * 2d, + LineJoin = PenLineJoin.Round, + StartLineCap = PenLineCap.Round, + EndLineCap = PenLineCap.Round + }); } return true; diff --git a/SampleApps/WpfApplication/WpfApplication.csproj b/SampleApps/WpfApplication/WpfApplication.csproj index 72585445..2d2773ec 100644 --- a/SampleApps/WpfApplication/WpfApplication.csproj +++ b/SampleApps/WpfApplication/WpfApplication.csproj @@ -16,12 +16,18 @@ + + + + - + + Always + @@ -29,4 +35,9 @@ + + + PreserveNewest + + \ No newline at end of file