using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Drawing; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Imaging; using Comal.Classes; using ExCSS; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.DynamicGrid; using InABox.Wpf; using InABox.WPF; using Syncfusion.UI.Xaml.Diagram; using Syncfusion.UI.Xaml.Maps; using GeoFence = Comal.Classes.GeoFence; using Point = System.Windows.Point; namespace PRSDesktop; public abstract class AbstractMapMarker { public string Label { get; set; } = ""; public string Longitude { get; set; } public string Latitude { get; set; } } public class SiteMapMarker : AbstractMapMarker { } public class AssetMapMarker : AbstractMapMarker { } public class GeoFenceImageConverter : AbstractConverter { private Dictionary _images = new() { { GPSLocationType.Office, PRSDesktop.Resources.mapmarker.AsBitmapImage() }, { GPSLocationType.Supplier, PRSDesktop.Resources.supplier.AsBitmapImage() }, { GPSLocationType.Employee, PRSDesktop.Resources.employee.AsBitmapImage() }, { GPSLocationType.Job, PRSDesktop.Resources.project.AsBitmapImage() }, }; public override BitmapImage? Convert(GPSLocationType value) { if (_images.TryGetValue(value, out var image)) return image; return null; } } public class LiveMapsPanelViewModel : DependencyObject, INotifyPropertyChanged { private EquipmentGroup[]? _equipmentGroups; private Equipment[]? _equipment; private EquipmentGroup? _selectedEquipmentGroup; private System.Tuple _selectedSiteType; private GeoFence? _selectedSite; private string? _equipmentSearch; private DateTime _date = DateTime.Today; private Equipment? _selectedEquipment; private ObservableCollection? _waypoints; private Point _center; private double _radius; private GeoFence[]? _sites; private ObservableCollection _elements; private ObservableCollection? _markers; #region Equipment public EquipmentGroup[]? EquipmentGroups { get => _equipmentGroups; set => SetField(ref _equipmentGroups, value); } public EquipmentGroup? SelectedEquipmentGroup { get => _selectedEquipmentGroup; set { if (value == _selectedEquipmentGroup) return; SetField(ref _selectedEquipmentGroup, value); SelectedEquipment = null; SaveSettings(); OnPropertyChanged(nameof(VisibleEquipment)); ReloadLocations(); } } public Equipment[]? Equipment { get => _equipment; set => SetField(ref _equipment, value); } public string? EquipmentSearch { get => _equipmentSearch; set { SetField(ref _equipmentSearch, value); OnPropertyChanged(nameof(VisibleEquipment)); } } public Equipment[]? VisibleEquipment => Equipment?.Where(x => ( (Equals(SelectedEquipmentGroup?.ID,CoreUtils.FullGuid) || Equals(x.GroupLink.ID,SelectedEquipmentGroup?.ID) ) && ( string.IsNullOrEmpty(EquipmentSearch) || x.Code.Contains(EquipmentSearch,StringComparison.CurrentCultureIgnoreCase) || x.Description.Contains(EquipmentSearch,StringComparison.CurrentCultureIgnoreCase) ) )).ToArray(); public Equipment? SelectedEquipment { get => _selectedEquipment; set { SetField(ref _selectedEquipment, value); if (value != null) { SelectedSite = null; ReloadLocations(); } } } #endregion #region Sites public System.Tuple[] SiteTypes => [ new System.Tuple(null, "All Sites"), new System.Tuple(GPSLocationType.Office, "Office Location"), new System.Tuple(GPSLocationType.Job, "Job Sites"), new System.Tuple(GPSLocationType.Supplier, "Suppliers") ]; public System.Tuple SelectedSiteType { get => _selectedSiteType; set { SetField(ref _selectedSiteType, value); SaveSettings(); OnPropertyChanged(nameof(VisibleSites)); SelectedSite = null; } } public GeoFence[]? Sites { get => _sites; set { _sitesMap.Clear(); if (value is not null) { foreach (var site in value ?? []) _sitesMap[site] = Serialization.Deserialize(site.Geofence) ?? new GeoFenceDefinition(); } SetField(ref _sites, value); } } public GeoFence[]? VisibleSites => Sites? .Where(x => (_selectedSiteType?.Item1 == null) || Equals(x.Type, _selectedSiteType?.Item1)) .ToArray(); private Dictionary _sitesMap = new(); public GeoFence? SelectedSite { get => _selectedSite; set { SetField(ref _selectedSite, value); if (value != null) { SelectedEquipment = null; CenterMap(value, null); } } } #endregion public DateTime Date { get => _date; set { SetField(ref _date, value); ReloadLocations(); } } private void CenterMap(GeoFence? site, IEnumerable? points) { var nwLon = double.MaxValue; var nwLat = double.MinValue; var seLon = double.MinValue; var seLat = double.MaxValue; var allpoints = new List(); if (points != null) allpoints.AddRange(points); if (site != null) { foreach (var fence in _sitesMap.Where(x=>x.Key == site)) allpoints.AddRange(fence.Value.Coordinates.Select(x => new Point(x.Latitude, x.Longitude))); } foreach (var point in allpoints) { var lat = point.X; var lon = point.Y; if (lat != 0.0F && lon != 0.00) { nwLat = lat > nwLat ? lat : nwLat; seLat = lat < seLat ? lat : seLat; nwLon = lon < nwLon ? lon : nwLon; seLon = lon > seLon ? lon : seLon; } } var cLat = (nwLat + seLat) / 2.0F; var cLon = (nwLon + seLon) / 2.0F; //Center = new Point(-33.0 + new Random(DateTime.Now.Millisecond).NextDouble(), 115.0 + new Random(DateTime.Now.Millisecond).NextDouble()); Center = new Point(cLat, cLon); var c = new Location { Latitude = cLat, Longitude = cLon }; var nw = new Location { Latitude = nwLat, Longitude = nwLon }; Radius = Math.Max(0.25, c.DistanceTo(nw, UnitOfLength.Kilometers) / 2.0F); } private void ReloadLocations() { var trackerids = VisibleEquipment?.Select(x => x.TrackerLink.ID).ToArray() ?? []; var selected = SelectedEquipment?.TrackerLink.ID ?? CoreUtils.FullGuid; Client.Query( new Filter(x=>x.Tracker.ID).InList(trackerids) .And(x => x.Location.Timestamp).IsGreaterThanOrEqualTo(Date.Date) .And(x => x.Location.Timestamp).IsLessThan(Date.Date.AddDays(1)), Columns.None() .Add(x=>x.Tracker.ID) .Add(x=>x.Location.Latitude) .Add(x=>x.Location.Longitude) .Add(x=>x.Location.Address) .Add(x=>x.Location.Timestamp), new SortOrder(x=>x.Location.Timestamp,SortDirection.Ascending), (data, _) => { Dispatcher.BeginInvoke(() => { GPSTrackerLocation[] pings = []; if (data is null) { EquipmentColorConverter.Pings = pings; Waypoints = null; Markers = null; } else { List markers = new(); pings = data.ToArray(); EquipmentColorConverter.Pings = pings; var otherassets = pings .Where(x=>x.Tracker.ID != selected) .GroupBy(x => x.Tracker.ID) .Where(x=>x.Any()) .Select(x=>x.Last() ); var assetmarkers = new List(); foreach (var asset in otherassets) { var eq = _equipment?.FirstOrDefault(e=>e.TrackerLink.ID == asset.Tracker.ID); if (eq != null) { var marker = new AssetMapMarker() { Latitude = $"{asset.Location.Latitude}", Longitude = $"{asset.Location.Longitude}", Label = $"{eq.Code}\n{eq.Description}\nLast seen: {asset.Location.Timestamp}" }; markers.Add(marker); } } var waypoints = pings .Where(x => x.Tracker.ID == selected) .ToArray(); Waypoints = new ObservableCollection(waypoints.Select(x => new Point(x.Location.Latitude, x.Location.Longitude))); var sites = new Dictionary(); GeoFence? curFence = null; foreach (var waypoint in waypoints) { var time = $"{waypoint.Location.Timestamp:h:mm tt}"; var geopoint = new GeoPoint(waypoint.Location.Latitude, waypoint.Location.Longitude); bool bFound = false; foreach (var geofence in _sitesMap) { if (geofence.Value.Contains(geopoint)) { if (!sites.ContainsKey(geofence.Key)) sites[geofence.Key] = new SiteMapMarker() { Latitude = $"{geopoint.Latitude}", Longitude = $"{geopoint.Longitude}" }; var timelist = sites[geofence.Key].Label.Split('\n').Where(x=>!string.IsNullOrWhiteSpace(x)).ToList(); var first = timelist.FirstOrDefault() ?? ""; if (!string.Equals(first,geofence.Key.Name)) timelist.Insert(0,geofence.Key.Name); if ((geofence.Key == curFence) && timelist.Any()) { var lasttime = timelist.Last().Split(" - ").ToList(); timelist.Remove(timelist.Last()); timelist.Add($"{lasttime.First()} - {time}"); } else timelist.Add($"{time} - {time}"); sites[geofence.Key].Label = string.Join("\n", timelist); curFence = geofence.Key; bFound = true; break; } } if (!bFound) curFence = null; } markers.AddRange(sites.Values); Markers = new ObservableCollection(markers); } OnPropertyChanged(nameof(VisibleEquipment)); RecalculateLayers(); CenterMap(_sites?.FirstOrDefault(x=>x.Type == GPSLocationType.Office), pings.Select(x=>new Point(x.Location.Latitude, x.Location.Longitude))); }); } ); } public ObservableCollection Elements { get => _elements; set => SetField(ref _elements, value); } private void RecalculateLayers() { var elements = new ObservableCollection(); if (_sites?.Any() == true) { foreach (var geofence in _sites) { var definition = Serialization.Deserialize(geofence.Geofence) ?? new GeoFenceDefinition(); var polygon = new MapPolygon() { Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.LightSalmon) { Opacity = 0.5 }, Stroke = System.Windows.Media.Brushes.Firebrick, StrokeThickness = 0.75, Points = new ObservableCollection( definition.Coordinates.Select(x => new Point(x.Latitude, x.Longitude))), }; elements.Add(polygon); } } if (_waypoints?.Any( ) == true) { var line = new MapPolyline() { Points = new ObservableCollection(new ObservableCollection(_waypoints)), Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Navy) { Opacity = 0.5 }, Stroke = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Navy) { Opacity = 0.5 }, StrokeThickness = 4 }; elements.Add(line); if (_waypoints.Count > 1) { var start = new MapCircle() { Center = _waypoints.First(), Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.LightGreen) { Opacity = 0.9 }, Stroke = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Black), StrokeThickness = 0.5, Radius = 10 }; elements.Add(start); } var end = new MapCircle() { Center = _waypoints.Last(), Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red) { Opacity = 0.9 }, Stroke = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Black), StrokeThickness = 0.5, Radius = 10 }; elements.Add(end); } Elements = elements; } public ICommand RefreshCommand { get; set; } public ObservableCollection? Markers { get => _markers; set => SetField(ref _markers, value); } public ObservableCollection? Waypoints { get => _waypoints; set => SetField(ref _waypoints, value); } public Point Center { get => _center; set => SetField(ref _center, value); } public double Radius { get => _radius; set => SetField(ref _radius, value); } public LiveMapsSettings Settings { get; private set; } public LiveMapsPanelViewModel() { Settings = new UserConfiguration().Load(); } public void SaveSettings() { if (bRefreshing) return; Settings.GeofenceType = _selectedSiteType.Item1; Settings.GroupID = _selectedEquipmentGroup?.ID ?? Guid.Empty; new UserConfiguration().Save(Settings); } private bool bRefreshing = false; public void Refresh() { MultiQuery query = new MultiQuery(); query.Add(new Filter().All()); query.Add(new Filter().All(), Columns.None() .Add(x => x.ID) .Add(x => x.Description) .Add(x=>x.Thumbnail.ID) ); query.Add( new Filter(x=>x.ID).InQuery(new Filter().All(),x=>x.Thumbnail.ID)); query.Add( new Filter(x => x.TrackerLink.ID).IsNotEqualTo(Guid.Empty), Columns.None() .Add(x => x.ID) .Add(x => x.Code) .Add(x => x.Description) .Add(x=>x.GroupLink.ID) .Add(x=>x.GroupLink.Thumbnail.ID) .Add(x=>x.TrackerLink.ID) .Add(x=>x.TrackerLink.Location.Timestamp) .Add(x=>x.TrackerLink.Location.Address) .Add(x=>x.TrackerLink.DeviceID) .Add(x=>x.TrackerLink.BatteryLevel) ); query.Query( _ => { Dispatcher.BeginInvoke(() => { bRefreshing = true; foreach (var row in query.Get().Rows) { var img = ImageUtils.LoadImage(row.Get(x => x.Data)); EquipmentThumbnailConverter.Cache[row.Get(x => x.ID)] = img; } // EquipmentThumbnailConverter.Cache = query.Get() // .ToDictionary(x => x.ID, // x => ImageUtils.LoadImage(x.Data)); Sites = query.Get().ToArray(); SelectedSiteType = SiteTypes.FirstOrDefault(x => x.Item1 == Settings.GeofenceType) ?? SiteTypes.First(); SelectedSite = null; var groups = query.Get().ToList(); groups.Insert(0,new EquipmentGroup() { ID = CoreUtils.FullGuid, Description="(All Groups)"}); EquipmentGroups = groups.ToArray(); SelectedEquipmentGroup = EquipmentGroups?.FirstOrDefault(x=>x.ID == Settings.GroupID); Equipment = query.Get().ToArray(); SelectedEquipment = null; bRefreshing = false; ReloadLocations(); }); }); } public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } }