using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.Linq; using System.Linq.Expressions; using System.Windows; using System.Windows.Threading; using Comal.Classes; using InABox.Clients; using InABox.Core; using Inflector; using Syncfusion.UI.Xaml.Maps; using Point = System.Windows.Point; namespace PRSDesktop; public enum LiveMapView { All, Selected } public class MapViewModel : DependencyObject { static readonly DependencyProperty ViewProperty = DependencyProperty.Register( nameof(View), typeof(LiveMapView), typeof(MapViewModel), new PropertyMetadata(LiveMapView.All) ); public LiveMapView View { get => (LiveMapView)GetValue(ViewProperty); set { SetValue(ViewProperty, value); Refresh(); } } static readonly DependencyProperty StyleProperty = DependencyProperty.Register( nameof(Style), typeof(LiveMapStyle), typeof(MapViewModel), new PropertyMetadata(LiveMapStyle.OSM) ); public LiveMapStyle Style { get => (LiveMapStyle)GetValue(StyleProperty); set => SetValue(StyleProperty, value); } static readonly DependencyProperty ApiKeyProperty = DependencyProperty.Register( nameof(ApiKey), typeof(string), typeof(MapViewModel), new PropertyMetadata("") ); public string ApiKey { get => (string)GetValue(ApiKeyProperty); set => SetValue(ApiKeyProperty, value); } static readonly DependencyProperty LayersProperty = DependencyProperty.Register( nameof(Layers), typeof(ObservableCollection), typeof(MapViewModel), new PropertyMetadata(new ObservableCollection()) ); public ObservableCollection Layers => (ObservableCollection)GetValue(LayersProperty); static readonly DependencyProperty EntityTypesProperty = DependencyProperty.Register( nameof(EntityTypes), typeof(Dictionary), typeof(MapViewModel), new PropertyMetadata(new Dictionary()) ); public Dictionary EntityTypes => (Dictionary)GetValue(EntityTypesProperty); static readonly DependencyProperty EntityTypeProperty = DependencyProperty.Register( nameof(EntityType), typeof(Type), typeof(MapViewModel) ); public Type? EntityType { get => (Type) GetValue(EntityTypeProperty); set { SetValue(EntityTypesProperty, value); ResetGroups(); } } static readonly DependencyProperty EntityGroupsProperty = DependencyProperty.Register( nameof(EntityGroups), typeof(Dictionary), typeof(MapViewModel), new PropertyMetadata(new Dictionary()) ); public Dictionary EntityGroups => (Dictionary)GetValue(EntityGroupsProperty); static readonly DependencyProperty EntityGroupProperty = DependencyProperty.Register( nameof(EntityGroup), typeof(Guid), typeof(MapViewModel), new PropertyMetadata(Guid.Empty) ); public Guid EntityGroup { get => (Guid)GetValue(EntityGroupProperty); set { SetValue(EntityGroupProperty,value); Refresh(); } } //private readonly List _markers = new(); //private readonly List _waypoints = new(); //private MapMarker? _selected; static readonly DependencyProperty MarkersProperty = DependencyProperty.Register( nameof(Markers), typeof(ObservableCollection), typeof(MapViewModel), new PropertyMetadata(new ObservableCollection()) ); public ObservableCollection Markers => (ObservableCollection)GetValue(MarkersProperty); static readonly DependencyProperty SelectedMarkerProperty = DependencyProperty.Register( nameof(SelectedMarker), typeof(MapMarker), typeof(MapViewModel) ); public MapMarker? SelectedMarker { get => GetValue(SelectedMarkerProperty) as MapMarker; set => SetValue(SelectedMarkerProperty, value); } static readonly DependencyProperty WayPointsProperty = DependencyProperty.Register( nameof(WayPoints), typeof(ObservableCollection), typeof(MapViewModel), new PropertyMetadata(new ObservableCollection()) ); public ObservableCollection WayPoints => (ObservableCollection)GetValue(WayPointsProperty); private LiveMapsSettings? _settings; public void Setup(LiveMapsSettings settings) { _settings = settings; LoadEntityTypes(); EntityType = !String.IsNullOrWhiteSpace(_settings.EntityType) ? EntityTypes.Any(x => String.Equals(x.Key.EntityName(), _settings.EntityType)) ? Type.GetType(_settings.EntityType) : null : null; EntityGroup = EntityGroups.Any(x => String.Equals(x.Key, _settings.EntityGroup)) ? _settings.EntityGroup : Guid.Empty; Style = _settings.MapStyle; ApiKey = _settings.ApiKey; } private void LoadEntityTypes() { if (ClientFactory.IsSupported()) EntityTypes[typeof(Equipment)] = typeof(Equipment).GetCaption().Pluralize(); if (ClientFactory.IsSupported()) EntityTypes[typeof(Job)] = typeof(Job).GetCaption().Pluralize(); if (ClientFactory.IsSupported()) EntityTypes[typeof(TimeSheet)] = typeof(TimeSheet).GetCaption().Pluralize(); if (ClientFactory.IsSupported()) EntityTypes[typeof(GPSTracker)] = typeof(GPSTracker).GetCaption().Pluralize(); } private void ResetGroups() { EntityGroups.Clear(); if (Type.Equals(EntityType,typeof(Equipment))) LoadGroups("All Equipment", x => x.Description); else if (Type.Equals(EntityType,typeof(Job))) LoadGroups("All Jobs", x => x.Description); else if (Type.Equals(EntityType,typeof(GPSTracker))) LoadGroups("All Trackers", x => x.Description); else if (Type.Equals(EntityType,typeof(TimeSheet))) { EntityGroups[Guid.Empty] = "Open TimeSheets"; EntityGroups[Guid.NewGuid()] = "TimeSheet Starts"; EntityGroups[CoreUtils.FullGuid] = "TimeSheet Finishes"; } } private void LoadGroups(string all, Expression> description) where TGroup : Entity, IRemotable, IPersistent, new() { EntityGroups.Add(Guid.Empty, all); var _groups = new Client().Query( LookupFactory.DefineFilter(), LookupFactory.DefineColumns(), LookupFactory.DefineSort() ); foreach (var _row in _groups.Rows) EntityGroups[_row.Get(x => x.ID)] = _row.Get(description); } public void Refresh() { Markers.Clear(); WayPoints.Clear(); if (View == LiveMapView.All) LoadMarkers(); else if (SelectedMarker != null) { LoadWayPoints(); foreach (var waypoint in _waypoints) { Markers.Add(waypoint); WayPoints.Add(new Point(double.Parse(waypoint.Longitude), double.Parse(waypoint.Latitude))); } } } public void LoadMarkers() { if (EntityType != null) { if (EntityType.Equals("Equipment")) { var filter = new Filter(x => x.TrackerLink.Location.Timestamp).IsNotEqualTo(DateTime.MinValue); if (!Security.IsAllowed()) filter = filter.And(x => x.Private).IsEqualTo(false); if (EntityGroup != Guid.Empty) filter = filter.And(x => x.GroupLink.ID).IsEqualTo(EntityGroup); LoadMarkers( filter, x => x.ID, x => x.Code, x => x.Description, x => x.TrackerLink.DeviceID, x => x.TrackerLink.Location.Latitude, x => x.TrackerLink.Location.Longitude, x => x.TrackerLink.Description, x => x.TrackerLink.Location.Timestamp ); } else if (EntityType.Equals("Jobs")) { var filter = new Filter(x => x.SiteAddress.Location.Timestamp).IsNotEqualTo(DateTime.MinValue); if (EntityGroup != Guid.Empty) filter = filter.And(x => x.JobStatus.ID).IsEqualTo(EntityGroup); LoadMarkers( filter, x => x.ID, x => x.JobNumber, x => x.Name, null, x => x.SiteAddress.Location.Latitude, x => x.SiteAddress.Location.Longitude, x => x.LastUpdateBy, x => x.SiteAddress.Location.Timestamp ); } else if (EntityType.Equals("TimeSheets")) { if (EntityGroup == Guid.Empty) // Starts LoadMarkers( new Filter(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.StartLocation.Timestamp) .IsNotEqualTo(new TimeSpan(0)), x => x.EmployeeLink.ID, x => x.EmployeeLink.Code, x => x.EmployeeLink.Name, null, x => x.StartLocation.Latitude, x => x.StartLocation.Longitude, x => x.SoftwareVersion, x => x.StartLocation.Timestamp ); else if (EntityGroup == CoreUtils.FullGuid) // Finishes LoadMarkers( new Filter(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.Finish).IsNotEqualTo(new TimeSpan(0)) .And(x => x.FinishLocation.Timestamp).IsNotEqualTo(new TimeSpan(0)), x => x.EmployeeLink.ID, x => x.EmployeeLink.Code, x => x.EmployeeLink.Name, null, x => x.FinishLocation.Latitude, x => x.FinishLocation.Longitude, x => x.SoftwareVersion, x => x.FinishLocation.Timestamp ); else // Open LoadMarkers( new Filter(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.Finish).IsEqualTo(new TimeSpan(0)) .And(x => x.StartLocation.Timestamp).IsNotEqualTo(new TimeSpan(0)), x => x.EmployeeLink.ID, x => x.EmployeeLink.Code, x => x.EmployeeLink.Name, null, x => x.StartLocation.Latitude, x => x.StartLocation.Longitude, x => x.SoftwareVersion, x => x.StartLocation.Timestamp ); } else if (EntityType.Equals("Trackers")) { var filter = new Filter(x => x.Location.Timestamp).IsNotEqualTo(DateTime.MinValue); if (EntityGroup != Guid.Empty) filter = filter.And(x => x.Type.ID).IsEqualTo(EntityGroup); LoadMarkers( filter, x => x.ID, x => x.DeviceID, x => x.Description, x => x.DeviceID, x => x.Location.Latitude, x => x.Location.Longitude, x => x.LastUpdateBy, x => x.Location.Timestamp ); } } } private class DbMarker { public DbMarker(string code, string description, DateTime timestamp, double latitude, double longitude, string updatedby, string deviceid) { Code = code; Description = description; TimeStamp = timestamp; Latitude = latitude; Longitude = longitude; UpdatedBy = updatedby; DeviceID = deviceid; } public string Code { get; } public string Description { get; } public DateTime TimeStamp { get; } public double Latitude { get; } public double Longitude { get; } public string UpdatedBy { get; } public string DeviceID { get; } } private void LoadMarkers(Filter filter, Expression> guid, Expression>? code, Expression> description, Expression>? deviceid, Expression> latitude, Expression> longitude, Expression> updatedby, Expression> timestamp, bool first = true) where T : Entity, IRemotable, IPersistent, new() { var _columns = new Columns(x=>x.ID) .Add(guid) .Add(description) .Add(latitude) .Add(longitude) .Add(timestamp) .Add(updatedby); if (code != null) _columns.Add(code); if (deviceid != null && !_columns.ColumnNames().Contains(CoreUtils.GetFullPropertyName(deviceid, "."))) _columns.Add(deviceid); new Client().Query( filter, _columns, null, (table, error) => { Dispatcher.Invoke(() => { if (error != null) { MessageBox.Show(error.Message); } else if (table != null) { var _values = new Dictionary(); var _groups = table.Rows .OrderBy(x=>x.Get(code ?? description)) .ThenBy(x=>x.Get(timestamp)) .Select(x => x.ToObject()) .GroupBy(x => x.ID); foreach (var _group in _groups) { var last = _group.OrderBy(timefunc).LastOrDefault(); if (last != null) Markers.Add( new MapMarker { ID = last.ID, Label = last., Latitude = string.Format("{0:F6}", _values[key].Latitude), Longitude = string.Format("{0:F6}", _values[key].Longitude), Description = _values[key].Description, Updated = _values[key].TimeStamp, UpdatedBy = _values[key].UpdatedBy, DeviceID = _values[key].DeviceID } ); } foreach (var _row in table.Rows) { var _id = _row.Get(guid); var _time = _row.Get(timestamp); if (!_values.ContainsKey(_id) || (first ? _values[_id].TimeStamp < _time : _values[_id].TimeStamp > _time)) _values[_id] = new DbMarker( code != null ? _row.Get(code) : "", _row.Get(description), _time, _row.Get(latitude), _row.Get(longitude), _row.Get(updatedby), deviceid != null ? _row.Get(deviceid) : "" ); } foreach (var key in _values.Keys) Markers.Add( new MapMarker { ID = key, Label = _values[key].Code, Latitude = string.Format("{0:F6}", _values[key].Latitude), Longitude = string.Format("{0:F6}", _values[key].Longitude), Description = _values[key].Description, Updated = _values[key].TimeStamp, UpdatedBy = _values[key].UpdatedBy, DeviceID = _values[key].DeviceID } ); } ResetZoom(); LoadMarkerList(); }); } ); } private void ResetZoom() { var nwLon = double.MaxValue; var nwLat = double.MinValue; var seLon = double.MinValue; var seLat = double.MaxValue; foreach (var marker in _markers) { var lat = double.Parse(marker.Latitude); var lon = double.Parse(marker.Longitude); 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(cLat, cLon); var nw = new Location { Latitude = nwLat, Longitude = nwLon }; var c = new Location { Latitude = cLat, Longitude = cLon }; Radius = c.DistanceTo(nw, UnitOfLength.Kilometers) / 2.0F; } public double Radius { get; set; } public Point Center { get; set; } }