< Summary

Information
Class: OffRouteMap.MainViewModel
Assembly: OffRouteMap
File(s): D:\a\OffRouteMap\OffRouteMap\MainViewModel.cs
Tag: 5_18712131505
Line coverage
32%
Covered lines: 63
Uncovered lines: 131
Coverable lines: 194
Total lines: 457
Line coverage: 32.4%
Branch coverage
24%
Covered branches: 16
Total branches: 66
Branch coverage: 24.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

D:\a\OffRouteMap\OffRouteMap\MainViewModel.cs

#LineLine coverage
 1using GMap.NET;
 2using GMap.NET.MapProviders;
 3using GMap.NET.WindowsPresentation;
 4using OffRouteMap.Properties;
 5using Ookii.Dialogs.Wpf;
 6using System.ComponentModel;
 7using System.Globalization;
 8using System.Net.NetworkInformation;
 9using System.Text;
 10using System.Windows.Input;
 11using System.Windows.Media;
 12
 13namespace OffRouteMap
 14{
 15    /// <summary>
 16    /// MainViewModel implements the UI functions joins them with data and files.
 17    /// </summary>
 18    public class MainViewModel : INotifyPropertyChanged
 19    {
 20
 21        private string _windowTitle;
 22        private double _guiZoomFactor;
 23        private string _statusLine;
 24        private string _selectedMap;
 25        private string _cacheRoot;
 26
 27        private PointLatLng _mouseDownPos;
 28        private List<PointLatLng> _routePoints;
 29        private GMapRoute _route;
 30
 31        private readonly IGMapControl _gmapControl;
 32
 33        private readonly FolderDialogService _folderDialogService;
 34
 35        public event PropertyChangedEventHandler PropertyChanged;
 036        public ICommand GuiZoomInCommand => new RelayCommand(GuiZoomIn);
 037        public ICommand GuiZoomOutCommand => new RelayCommand(GuiZoomOut);
 038        public ICommand BeforeClosingCommand => new RelayCommand(BeforeClosing);
 039        public ICommand SetCacheRootCommand => new RelayCommand(SetCacheRoot);
 040        public ICommand RemoveRouteCommand => new RelayCommand(RemoveRoute);
 041        public ICommand LoadRouteCommand => new RelayCommand(LoadRoute);
 042        public ICommand SaveRouteCommand => new RelayCommand(SaveRoute);
 43
 444        public ProviderCollection Items { get; }
 45
 46        public string WindowTitle
 47        {
 448            get => _windowTitle;
 49            set
 50            {
 1051                if (value != null)
 52                {
 853                    _windowTitle = value;
 854                    OnPropertyChanged(nameof(WindowTitle));
 55                }
 1056            }
 57        }
 58
 59        public string StatusLine
 60        {
 061            get => _statusLine;
 62            set
 63            {
 464                if (value != null)
 65                {
 466                    _statusLine = value;
 467                    OnPropertyChanged(nameof(StatusLine));
 68                }
 469            }
 70        }
 71
 72        public double GuiZoomFactor
 73        {
 074            get { return _guiZoomFactor; }
 75            set
 76            {
 477                if (_guiZoomFactor != value)
 78                {
 479                    _guiZoomFactor = value;
 480                    OnPropertyChanged(nameof(GuiZoomFactor));
 81                }
 482            }
 83        }
 84        public string SelectedMap
 85        {
 086            get => _selectedMap;
 87            set
 88            {
 489                _selectedMap = value;
 490                OnPropertyChanged(nameof(SelectedMap));
 491                HandleMapChanges();
 492            }
 93        }
 94        protected void OnPropertyChanged (string propertyName)
 95        {
 2096            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 297        }
 98
 99        /// <summary>
 100        /// The Model Constructor needs the map control object.
 101        /// </summary>
 102        /// <param name="gmapControl">most functions of the UI are focused on the MapControl</param>
 4103        public MainViewModel (IGMapControl gmapControl) {
 104
 105            // @todo make this init more configurable
 106
 107            // for testing
 108            //Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");
 109
 4110            WindowTitle = GetType().Namespace;
 4111            _gmapControl = gmapControl;
 112
 4113            _folderDialogService = new FolderDialogService();
 4114            _cacheRoot = Settings.Default.cacheRoot;
 4115            GuiZoomFactor = Settings.Default.guiSize;
 116
 4117            Items = new ProviderCollection(new[]
 4118            {
 4119                new ProviderItem("OSM",          "OpenStreetMap", OpenStreetMapProvider.Instance),
 4120                new ProviderItem("googlemaps",   "Google Maps",   GMapProviders.GoogleMap),
 4121                new ProviderItem("opencyclemap", "Cycle Maps",    GMapProviders.OpenCycleMap),
 4122                new ProviderItem("BingHybrid",   "Bing Hybrid",   GMapProviders.BingHybridMap)
 4123            });
 4124            SelectedMap = Settings.Default.lastMap;
 4125            _gmapControl.Zoom = Settings.Default.lastZoom;
 4126            _gmapControl.ShowCenter = false;
 4127            _gmapControl.CanDragMap = true;
 4128            _gmapControl.DragButton = MouseButton.Left;
 129
 4130            _gmapControl.Position = new PointLatLng(
 4131                Settings.Default.lastLatitude,
 4132                Settings.Default.lastLongitude
 4133            );
 134
 135            //_gmapControl.MouseDoubleClick += OnMouseDoubleDownClick;
 136            //_gmapControl.MouseRightButtonDown += OnMouseRightClick;
 137            //_gmapControl.MouseMove += OnMouseMove;
 4138            OnPositionChanged(_gmapControl.Position);
 4139        }
 140
 141        /// <summary>
 142        /// UI Button function to make the UI bigger.
 143        /// </summary>
 144        public void GuiZoomIn ()
 145        {
 0146            GuiZoomFactor *= 1.1;
 0147            Settings.Default.guiSize = _guiZoomFactor;
 0148            Settings.Default.Save();
 0149        }
 150
 151        /// <summary>
 152        /// UI Button function to make the UI smaller.
 153        /// </summary>
 154        public void GuiZoomOut ()
 155        {
 0156            GuiZoomFactor /= 1.1;
 0157            Settings.Default.guiSize = _guiZoomFactor;
 0158            Settings.Default.Save();
 0159        }
 160
 161        /// <summary>
 162        /// UI Button function to set the path, where the map tile cache has to store the tiles for each map provider.
 163        /// </summary>
 164        public void SetCacheRoot ()
 165        {
 0166            var path = _folderDialogService.ShowSelectFolderDialog(
 0167                Strings.FolderDialog_Title,
 0168                _cacheRoot
 0169            );
 0170            if (path != null && path != _cacheRoot)
 171            {
 0172                _cacheRoot = path;
 0173                Settings.Default.cacheRoot = _cacheRoot;
 0174                Settings.Default.Save();
 0175                HandleMapChanges();
 176            }
 0177        }
 178
 179        /// <summary>
 180        /// This method sets map provider and cache based on the _selectedMap and settings.
 181        /// </summary>
 182        private void HandleMapChanges ()
 183        {
 4184            if (_cacheRoot == "")
 185            {
 4186                _cacheRoot = System.IO.Path.Combine(
 4187                    Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
 4188                    "Maps"
 4189                );
 190            }
 4191            string cachePath = System.IO.Path.Combine(_cacheRoot, _selectedMap);
 4192            _gmapControl.PrimaryCache = new FileCacheProvider(cachePath);
 193
 4194            if (NetworkInterface.GetIsNetworkAvailable())
 195            {
 4196                _gmapControl.CacheMode = AccessMode.ServerAndCache;
 197            }
 198            else
 199            {
 0200                _gmapControl.CacheMode = AccessMode.CacheOnly;
 201            }
 202
 4203            if (Items.TryGet(_selectedMap, out var item))
 204            {
 4205                _gmapControl.MapProvider = item.Provider;
 206            }
 4207        }
 208
 209        /// <summary>
 210        /// Store position, map zoom and map provider before closing the application.
 211        /// This method is called by the UI in the OnClose Event.
 212        /// </summary>
 213        private void BeforeClosing ()
 214        {
 0215            Settings.Default.lastLatitude = _gmapControl.Position.Lat;
 0216            Settings.Default.lastLongitude = _gmapControl.Position.Lng;
 0217            Settings.Default.lastZoom = (int)_gmapControl.Zoom;
 0218            Settings.Default.lastMap = _selectedMap;
 0219            Settings.Default.Save();
 0220        }
 221
 222        /// <summary>
 223        /// This method refreshs the statusline with coordinates and route length.
 224        /// It is called by mouse events, which occurs on the mapControl object.
 225        /// </summary>
 226        /// <param name="point">the latitude and longitude calculated by the UI event.</param>
 227        private void OnPositionChanged (PointLatLng point)
 228        {
 4229            string formattedLat = point.Lat.ToString("F6", CultureInfo.InvariantCulture);
 4230            string formattedLng = point.Lng.ToString("F6", CultureInfo.InvariantCulture);
 231
 4232            double distance = RouteLengthKm();
 4233            if (distance > 0)
 234            {
 0235                string formattedDist = distance.ToString("F4", CultureInfo.InvariantCulture);
 0236                StatusLine = $"Lat Lng Route: {formattedLat} {formattedLng} {formattedDist} km";
 237            }
 238            else
 239            {
 4240                StatusLine = $"Lat Lng: {formattedLat} {formattedLng}";
 241            }
 4242        }
 243
 244        /// <summary>
 245        /// UI Button function to delete the route from the map.
 246        /// </summary>
 247        private void RemoveRoute()
 248        {
 0249            if (_route != null)
 250            {
 0251                _gmapControl.Markers.Remove(_route);
 0252                _route = null;
 253            }
 0254            _routePoints = new List<PointLatLng>();
 0255            StatusLine = "";
 0256        }
 257
 258        /// <summary>
 259        /// UI Button function to open a save dialog.
 260        /// </summary>
 261        private void SaveRoute ()
 262        {
 0263            if (_routePoints == null || _routePoints.Count == 0) return;
 264
 0265            var dlg = new VistaSaveFileDialog
 0266            {
 0267                Title = Strings.SaveDialog_Title,
 0268                Filter = Strings.Dialog_Filetype + " (*.txt)|*.txt",
 0269                DefaultExt = "txt",
 0270                FileName = "route.txt",
 0271                OverwritePrompt = true
 0272            };
 273
 0274            bool? result = dlg.ShowDialog();
 0275            if (result != true) return;
 276
 0277            var culture = CultureInfo.InvariantCulture;
 278
 0279            var sb = new StringBuilder();
 0280            double distance = 0.0;
 0281            for (int i = 0; i < _routePoints.Count; i++)
 282            {
 0283                double lat = _routePoints[i].Lat;
 0284                double lng = _routePoints[i].Lng;
 0285                if (i > 0)
 286                {
 0287                    distance += DistanceKm(_routePoints[i - 1], _routePoints[i]);
 288                }
 289
 0290                sb.AppendLine(string.Format(culture, "{0:F6},{1:F6},{2:F4}km", lat, lng, distance));
 291            }
 292
 0293            System.IO.File.WriteAllText(dlg.FileName, sb.ToString(), Encoding.UTF8);
 0294        }
 295
 296        /// <summary>
 297        /// UI Button function to open a file-open dialog to load a route.
 298        /// </summary>
 299        private void LoadRoute ()
 300        {
 0301            var dialog = new VistaOpenFileDialog
 0302            {
 0303                Title = Strings.LoadDialog_Title,
 0304                Filter = Strings.Dialog_Filetype + " (*.txt)|*.txt",
 0305                DefaultExt = "txt",
 0306                Multiselect = false,
 0307                CheckFileExists = true,
 0308                CheckPathExists = true
 0309            };
 310
 0311            bool? result = dialog.ShowDialog();
 312
 0313            if (result == true)
 314            {
 0315                var culture = CultureInfo.InvariantCulture;
 316
 0317                if (_route != null)
 318                {
 0319                    _gmapControl.Markers.Remove(_route);
 320                }
 0321                _routePoints = new List<PointLatLng>();
 322
 0323                foreach (var line in System.IO.File.ReadLines(dialog.FileName))
 324                {
 0325                    if (string.IsNullOrWhiteSpace(line)) continue;
 0326                    var parts = line.Split(new[] { ' ', ',', '\t' }, StringSplitOptions.RemoveEmptyEntries);
 0327                    if (parts.Length < 2)
 328                    {
 329                        continue;
 330                    }
 0331                    if (
 0332                        double.TryParse(parts[0], NumberStyles.Float | NumberStyles.AllowLeadingSign, culture, out doubl
 0333                        double.TryParse(parts[1], NumberStyles.Float | NumberStyles.AllowLeadingSign, culture, out doubl
 0334                    )
 335                    {
 0336                        _routePoints.Add(new PointLatLng(v1, v2));
 337                    }
 338                    // @todo error handling like a log or StatusLine
 339                }
 0340                if (_routePoints.Count > 1)
 341                {
 0342                    _gmapControl.Position = _routePoints.Last();
 0343                    ShowRoute();
 344                }
 345            }
 0346        }
 347
 348        /// <summary>
 349        /// This method is called on load route or if a new point is added to the route or a point is removed.
 350        /// </summary>
 351        private void ShowRoute ()
 352        {
 0353            if (_routePoints == null)
 354            {
 0355                _routePoints = new List<PointLatLng>();
 356            }
 357
 0358            if (_route != null)
 359            {
 0360                _gmapControl.Markers.Remove(_route);
 361            }
 362
 0363            if (_routePoints.Count > 0)
 364            {
 0365                _route = new GMapRoute(_routePoints);
 0366                _route.Shape = new System.Windows.Shapes.Path()
 0367                {
 0368                    Stroke = new SolidColorBrush(Colors.Blue),
 0369                    StrokeThickness = 2
 0370                };
 371
 0372                _gmapControl.Markers.Add(_route);
 373            }
 0374        }
 375
 376        /// <summary>
 377        /// Primitive methode to calculate the distance between to positions on the earth.
 378        /// </summary>
 379        /// <param name="p1">position 1</param>
 380        /// <param name="p2">position 2</param>
 381        /// <returns></returns>
 382        private double DistanceKm (PointLatLng p1, PointLatLng p2)
 383        {
 384            const double R = 6371.0; // Erdradius in km
 0385            double lat1 = DegreesToRadians(p1.Lat);
 0386            double lat2 = DegreesToRadians(p2.Lat);
 0387            double dLat = DegreesToRadians(p2.Lat - p1.Lat);
 0388            double dLon = DegreesToRadians(p2.Lng - p1.Lng);
 389
 0390            double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
 0391                       Math.Cos(lat1) * Math.Cos(lat2) *
 0392                       Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
 0393            double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
 0394            return R * c;
 395        }
 396
 397        /// <summary>
 398        /// converter used by DistanceKm() to get positions in radians and not degrees
 399        /// </summary>
 400        /// <param name="deg">angle by degrees</param>
 401        /// <returns>the angle in radians</returns>
 402        private double DegreesToRadians (double deg)
 403        {
 0404            return deg * (Math.PI / 180.0);
 405        }
 406
 407        /// <summary>
 408        /// method loops through _routePoints and calculate + sum the route distance.
 409        /// </summary>
 410        /// <returns>the complete distance of the route stored in _routePoints</returns>
 411        private double RouteLengthKm ()
 412        {
 8413            if (_routePoints == null || _routePoints.Count < 2) return 0.0;
 0414            double total = 0.0;
 0415            for (int i = 0; i < _routePoints.Count - 1; i++)
 416            {
 0417                total += DistanceKm(_routePoints[i], _routePoints[i + 1]);
 418            }
 0419            return total;
 420        }
 421
 422        /// <summary>
 423        /// Used by UI events to store the map positon on earth in _mouseDownPos
 424        /// </summary>
 425        /// <param name="point"></param>
 426        public void UpdateMousePositionFrom (System.Windows.Point point)
 427        {
 0428            _mouseDownPos = _gmapControl.FromLocalToLatLng((int)point.X, (int)point.Y);
 0429            OnPositionChanged(_mouseDownPos);
 0430        }
 431
 432        /// <summary>
 433        /// Used by UI event on mapControl object and add a point in _routePoints
 434        /// </summary>
 435        public void AddRoutePoint ()
 436        {
 437            // to modern for mono csc
 438            //_routePoints ??= new List<PointLatLng>();
 0439            if (_routePoints == null) _routePoints = new List<PointLatLng>();
 0440            _routePoints.Add(_mouseDownPos);
 0441            ShowRoute();
 0442        }
 443
 444        /// <summary>
 445        /// Used by UI event on mapControl object and remove a point in _routePoints
 446        /// </summary>
 447        public void RemoveLastRoutePoint ()
 448        {
 0449            if (_routePoints != null && _routePoints.Count > 0)
 450            {
 0451                _routePoints.RemoveAt(_routePoints.Count - 1);
 0452                ShowRoute();
 453            }
 0454        }
 455
 456    }
 457}