LiveMapsPanelViewModel.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.ComponentModel;
  5. using System.Drawing;
  6. using System.Globalization;
  7. using System.Linq;
  8. using System.Runtime.CompilerServices;
  9. using System.Windows;
  10. using System.Windows.Controls;
  11. using System.Windows.Input;
  12. using System.Windows.Media.Imaging;
  13. using Comal.Classes;
  14. using ExCSS;
  15. using InABox.Clients;
  16. using InABox.Configuration;
  17. using InABox.Core;
  18. using InABox.DynamicGrid;
  19. using InABox.Wpf;
  20. using InABox.WPF;
  21. using Syncfusion.UI.Xaml.Diagram;
  22. using Syncfusion.UI.Xaml.Maps;
  23. using GeoFence = Comal.Classes.GeoFence;
  24. using Point = System.Windows.Point;
  25. namespace PRSDesktop;
  26. public abstract class AbstractMapMarker
  27. {
  28. public string Label { get; set; } = "";
  29. public string Longitude { get; set; }
  30. public string Latitude { get; set; }
  31. }
  32. public class SiteMapMarker : AbstractMapMarker
  33. {
  34. }
  35. public class AssetMapMarker : AbstractMapMarker { }
  36. public class GeoFenceImageConverter : AbstractConverter<GPSLocationType, BitmapImage?>
  37. {
  38. private Dictionary<GPSLocationType, BitmapImage?> _images = new()
  39. {
  40. { GPSLocationType.Office, PRSDesktop.Resources.mapmarker.AsBitmapImage() },
  41. { GPSLocationType.Supplier, PRSDesktop.Resources.supplier.AsBitmapImage() },
  42. { GPSLocationType.Employee, PRSDesktop.Resources.employee.AsBitmapImage() },
  43. { GPSLocationType.Job, PRSDesktop.Resources.project.AsBitmapImage() },
  44. };
  45. public override BitmapImage? Convert(GPSLocationType value)
  46. {
  47. if (_images.TryGetValue(value, out var image))
  48. return image;
  49. return null;
  50. }
  51. }
  52. public class LiveMapsPanelViewModel : DependencyObject, INotifyPropertyChanged
  53. {
  54. private EquipmentGroup[]? _equipmentGroups;
  55. private Equipment[]? _equipment;
  56. private EquipmentGroup? _selectedEquipmentGroup;
  57. private System.Tuple<GPSLocationType?, string> _selectedSiteType;
  58. private GeoFence? _selectedSite;
  59. private string? _equipmentSearch;
  60. private DateTime _date = DateTime.Today;
  61. private Equipment? _selectedEquipment;
  62. private ObservableCollection<Point>? _waypoints;
  63. private Point _center;
  64. private double _radius;
  65. private GeoFence[]? _sites;
  66. private ObservableCollection<MapElement> _elements;
  67. private ObservableCollection<AbstractMapMarker>? _markers;
  68. #region Equipment
  69. public EquipmentGroup[]? EquipmentGroups
  70. {
  71. get => _equipmentGroups;
  72. set => SetField(ref _equipmentGroups, value);
  73. }
  74. public EquipmentGroup? SelectedEquipmentGroup
  75. {
  76. get => _selectedEquipmentGroup;
  77. set
  78. {
  79. if (value == _selectedEquipmentGroup)
  80. return;
  81. SetField(ref _selectedEquipmentGroup, value);
  82. SelectedEquipment = null;
  83. SaveSettings();
  84. OnPropertyChanged(nameof(VisibleEquipment));
  85. ReloadLocations();
  86. }
  87. }
  88. public Equipment[]? Equipment
  89. {
  90. get => _equipment;
  91. set => SetField(ref _equipment, value);
  92. }
  93. public string? EquipmentSearch
  94. {
  95. get => _equipmentSearch;
  96. set
  97. {
  98. SetField(ref _equipmentSearch, value);
  99. OnPropertyChanged(nameof(VisibleEquipment));
  100. }
  101. }
  102. public Equipment[]? VisibleEquipment => Equipment?.Where(x =>
  103. (
  104. (Equals(SelectedEquipmentGroup?.ID,CoreUtils.FullGuid)
  105. || Equals(x.GroupLink.ID,SelectedEquipmentGroup?.ID)
  106. ) && (
  107. string.IsNullOrEmpty(EquipmentSearch)
  108. || x.Code.Contains(EquipmentSearch,StringComparison.CurrentCultureIgnoreCase)
  109. || x.Description.Contains(EquipmentSearch,StringComparison.CurrentCultureIgnoreCase)
  110. )
  111. )).ToArray();
  112. public Equipment? SelectedEquipment
  113. {
  114. get => _selectedEquipment;
  115. set
  116. {
  117. SetField(ref _selectedEquipment, value);
  118. if (value != null)
  119. {
  120. SelectedSite = null;
  121. ReloadLocations();
  122. }
  123. }
  124. }
  125. #endregion
  126. #region Sites
  127. public System.Tuple<GPSLocationType?, string>[] SiteTypes =>
  128. [
  129. new System.Tuple<GPSLocationType?, string>(null, "All Sites"),
  130. new System.Tuple<GPSLocationType?, string>(GPSLocationType.Office, "Office Location"),
  131. new System.Tuple<GPSLocationType?, string>(GPSLocationType.Job, "Job Sites"),
  132. new System.Tuple<GPSLocationType?, string>(GPSLocationType.Supplier, "Suppliers")
  133. ];
  134. public System.Tuple<GPSLocationType?, string> SelectedSiteType
  135. {
  136. get => _selectedSiteType;
  137. set
  138. {
  139. SetField(ref _selectedSiteType, value);
  140. SaveSettings();
  141. OnPropertyChanged(nameof(VisibleSites));
  142. SelectedSite = null;
  143. }
  144. }
  145. public GeoFence[]? Sites
  146. {
  147. get => _sites;
  148. set
  149. {
  150. _sitesMap.Clear();
  151. if (value is not null)
  152. {
  153. foreach (var site in value ?? [])
  154. _sitesMap[site] = Serialization.Deserialize<GeoFenceDefinition>(site.Geofence) ?? new GeoFenceDefinition();
  155. }
  156. SetField(ref _sites, value);
  157. }
  158. }
  159. public GeoFence[]? VisibleSites => Sites?
  160. .Where(x => (_selectedSiteType?.Item1 == null) || Equals(x.Type, _selectedSiteType?.Item1))
  161. .ToArray();
  162. private Dictionary<GeoFence,GeoFenceDefinition> _sitesMap = new();
  163. public GeoFence? SelectedSite
  164. {
  165. get => _selectedSite;
  166. set
  167. {
  168. SetField(ref _selectedSite, value);
  169. if (value != null)
  170. {
  171. SelectedEquipment = null;
  172. CenterMap(value, null);
  173. }
  174. }
  175. }
  176. #endregion
  177. public DateTime Date
  178. {
  179. get => _date;
  180. set
  181. {
  182. SetField(ref _date, value);
  183. ReloadLocations();
  184. }
  185. }
  186. private void CenterMap(GeoFence? site, IEnumerable<Point>? points)
  187. {
  188. var nwLon = double.MaxValue;
  189. var nwLat = double.MinValue;
  190. var seLon = double.MinValue;
  191. var seLat = double.MaxValue;
  192. var allpoints = new List<Point>();
  193. if (points != null)
  194. allpoints.AddRange(points);
  195. if (site != null)
  196. {
  197. foreach (var fence in _sitesMap.Where(x=>x.Key == site))
  198. allpoints.AddRange(fence.Value.Coordinates.Select(x => new Point(x.Latitude, x.Longitude)));
  199. }
  200. foreach (var point in allpoints)
  201. {
  202. var lat = point.X;
  203. var lon = point.Y;
  204. if (lat != 0.0F && lon != 0.00)
  205. {
  206. nwLat = lat > nwLat ? lat : nwLat;
  207. seLat = lat < seLat ? lat : seLat;
  208. nwLon = lon < nwLon ? lon : nwLon;
  209. seLon = lon > seLon ? lon : seLon;
  210. }
  211. }
  212. var cLat = (nwLat + seLat) / 2.0F;
  213. var cLon = (nwLon + seLon) / 2.0F;
  214. //Center = new Point(-33.0 + new Random(DateTime.Now.Millisecond).NextDouble(), 115.0 + new Random(DateTime.Now.Millisecond).NextDouble());
  215. Center = new Point(cLat, cLon);
  216. var c = new Location { Latitude = cLat, Longitude = cLon };
  217. var nw = new Location { Latitude = nwLat, Longitude = nwLon };
  218. Radius = Math.Max(0.25, c.DistanceTo(nw, UnitOfLength.Kilometers) / 2.0F);
  219. }
  220. private void ReloadLocations()
  221. {
  222. var trackerids = VisibleEquipment?.Select(x => x.TrackerLink.ID).ToArray() ?? [];
  223. var selected = SelectedEquipment?.TrackerLink.ID ?? CoreUtils.FullGuid;
  224. Client.Query(
  225. new Filter<GPSTrackerLocation>(x=>x.Tracker.ID).InList(trackerids)
  226. .And(x => x.Location.Timestamp).IsGreaterThanOrEqualTo(Date.Date)
  227. .And(x => x.Location.Timestamp).IsLessThan(Date.Date.AddDays(1)),
  228. Columns.None<GPSTrackerLocation>()
  229. .Add(x=>x.Tracker.ID)
  230. .Add(x=>x.Location.Latitude)
  231. .Add(x=>x.Location.Longitude)
  232. .Add(x=>x.Location.Address)
  233. .Add(x=>x.Location.Timestamp),
  234. new SortOrder<GPSTrackerLocation>(x=>x.Location.Timestamp,SortDirection.Ascending),
  235. (data, _) =>
  236. {
  237. Dispatcher.BeginInvoke(() =>
  238. {
  239. GPSTrackerLocation[] pings = [];
  240. if (data is null)
  241. {
  242. EquipmentColorConverter.Pings = pings;
  243. Waypoints = null;
  244. Markers = null;
  245. }
  246. else
  247. {
  248. List<AbstractMapMarker> markers = new();
  249. pings = data.ToArray<GPSTrackerLocation>();
  250. EquipmentColorConverter.Pings = pings;
  251. var otherassets = pings
  252. .Where(x=>x.Tracker.ID != selected)
  253. .GroupBy(x => x.Tracker.ID)
  254. .Where(x=>x.Any())
  255. .Select(x=>x.Last()
  256. );
  257. var assetmarkers = new List<AssetMapMarker>();
  258. foreach (var asset in otherassets)
  259. {
  260. var eq = _equipment?.FirstOrDefault(e=>e.TrackerLink.ID == asset.Tracker.ID);
  261. if (eq != null)
  262. {
  263. var marker = new AssetMapMarker()
  264. {
  265. Latitude = $"{asset.Location.Latitude}",
  266. Longitude = $"{asset.Location.Longitude}",
  267. Label = $"{eq.Code}\n{eq.Description}\nLast seen: {asset.Location.Timestamp}"
  268. };
  269. markers.Add(marker);
  270. }
  271. }
  272. var waypoints = pings
  273. .Where(x => x.Tracker.ID == selected)
  274. .ToArray();
  275. Waypoints = new ObservableCollection<Point>(waypoints.Select(x => new Point(x.Location.Latitude, x.Location.Longitude)));
  276. var sites = new Dictionary<GeoFence, SiteMapMarker>();
  277. GeoFence? curFence = null;
  278. foreach (var waypoint in waypoints)
  279. {
  280. var time = $"{waypoint.Location.Timestamp:h:mm tt}";
  281. var geopoint = new GeoPoint(waypoint.Location.Latitude, waypoint.Location.Longitude);
  282. bool bFound = false;
  283. foreach (var geofence in _sitesMap)
  284. {
  285. if (geofence.Value.Contains(geopoint))
  286. {
  287. if (!sites.ContainsKey(geofence.Key))
  288. sites[geofence.Key] = new SiteMapMarker()
  289. {
  290. Latitude = $"{geopoint.Latitude}",
  291. Longitude = $"{geopoint.Longitude}"
  292. };
  293. var timelist = sites[geofence.Key].Label.Split('\n').Where(x=>!string.IsNullOrWhiteSpace(x)).ToList();
  294. var first = timelist.FirstOrDefault() ?? "";
  295. if (!string.Equals(first,geofence.Key.Name))
  296. timelist.Insert(0,geofence.Key.Name);
  297. if ((geofence.Key == curFence) && timelist.Any())
  298. {
  299. var lasttime = timelist.Last().Split(" - ").ToList();
  300. timelist.Remove(timelist.Last());
  301. timelist.Add($"{lasttime.First()} - {time}");
  302. }
  303. else
  304. timelist.Add($"{time} - {time}");
  305. sites[geofence.Key].Label = string.Join("\n", timelist);
  306. curFence = geofence.Key;
  307. bFound = true;
  308. break;
  309. }
  310. }
  311. if (!bFound)
  312. curFence = null;
  313. }
  314. markers.AddRange(sites.Values);
  315. Markers = new ObservableCollection<AbstractMapMarker>(markers);
  316. }
  317. OnPropertyChanged(nameof(VisibleEquipment));
  318. RecalculateLayers();
  319. CenterMap(_sites?.FirstOrDefault(x=>x.Type == GPSLocationType.Office), pings.Select(x=>new Point(x.Location.Latitude, x.Location.Longitude)));
  320. });
  321. }
  322. );
  323. }
  324. public ObservableCollection<MapElement> Elements
  325. {
  326. get => _elements;
  327. set => SetField(ref _elements, value);
  328. }
  329. private void RecalculateLayers()
  330. {
  331. var elements = new ObservableCollection<MapElement>();
  332. if (_sites?.Any() == true)
  333. {
  334. foreach (var geofence in _sites)
  335. {
  336. var definition = Serialization.Deserialize<GeoFenceDefinition>(geofence.Geofence) ?? new GeoFenceDefinition();
  337. var polygon = new MapPolygon()
  338. {
  339. Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.LightSalmon)
  340. { Opacity = 0.5 },
  341. Stroke = System.Windows.Media.Brushes.Firebrick,
  342. StrokeThickness = 0.75,
  343. Points = new ObservableCollection<Point>(
  344. definition.Coordinates.Select(x => new Point(x.Latitude, x.Longitude))),
  345. };
  346. elements.Add(polygon);
  347. }
  348. }
  349. if (_waypoints?.Any( ) == true)
  350. {
  351. var line = new MapPolyline()
  352. {
  353. Points = new ObservableCollection<Point>(new ObservableCollection<Point>(_waypoints)),
  354. Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Navy) { Opacity = 0.5 },
  355. Stroke = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Navy) { Opacity = 0.5 },
  356. StrokeThickness = 4
  357. };
  358. elements.Add(line);
  359. if (_waypoints.Count > 1)
  360. {
  361. var start = new MapCircle()
  362. {
  363. Center = _waypoints.First(),
  364. Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.LightGreen) { Opacity = 0.9 },
  365. Stroke = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Black),
  366. StrokeThickness = 0.5,
  367. Radius = 10
  368. };
  369. elements.Add(start);
  370. }
  371. var end = new MapCircle()
  372. {
  373. Center = _waypoints.Last(),
  374. Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red) { Opacity = 0.9 },
  375. Stroke = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Black),
  376. StrokeThickness = 0.5,
  377. Radius = 10
  378. };
  379. elements.Add(end);
  380. }
  381. Elements = elements;
  382. }
  383. public ICommand RefreshCommand { get; set; }
  384. public ObservableCollection<AbstractMapMarker>? Markers
  385. {
  386. get => _markers;
  387. set => SetField(ref _markers, value);
  388. }
  389. public ObservableCollection<Point>? Waypoints
  390. {
  391. get => _waypoints;
  392. set => SetField(ref _waypoints, value);
  393. }
  394. public Point Center
  395. {
  396. get => _center;
  397. set => SetField(ref _center, value);
  398. }
  399. public double Radius
  400. {
  401. get => _radius;
  402. set => SetField(ref _radius, value);
  403. }
  404. public LiveMapsSettings Settings { get; private set; }
  405. public LiveMapsPanelViewModel()
  406. {
  407. Settings = new UserConfiguration<LiveMapsSettings>().Load();
  408. }
  409. public void SaveSettings()
  410. {
  411. if (bRefreshing)
  412. return;
  413. Settings.GeofenceType = _selectedSiteType.Item1;
  414. Settings.GroupID = _selectedEquipmentGroup?.ID ?? Guid.Empty;
  415. new UserConfiguration<LiveMapsSettings>().Save(Settings);
  416. }
  417. private bool bRefreshing = false;
  418. public void Refresh()
  419. {
  420. MultiQuery query = new MultiQuery();
  421. query.Add(new Filter<GeoFence>().All());
  422. query.Add(new Filter<EquipmentGroup>().All(),
  423. Columns.None<EquipmentGroup>()
  424. .Add(x => x.ID)
  425. .Add(x => x.Description)
  426. .Add(x=>x.Thumbnail.ID)
  427. );
  428. query.Add(
  429. new Filter<Document>(x=>x.ID).InQuery(new Filter<EquipmentGroup>().All(),x=>x.Thumbnail.ID));
  430. query.Add(
  431. new Filter<Equipment>(x => x.TrackerLink.ID).IsNotEqualTo(Guid.Empty),
  432. Columns.None<Equipment>()
  433. .Add(x => x.ID)
  434. .Add(x => x.Code)
  435. .Add(x => x.Description)
  436. .Add(x=>x.GroupLink.ID)
  437. .Add(x=>x.GroupLink.Thumbnail.ID)
  438. .Add(x=>x.TrackerLink.ID)
  439. .Add(x=>x.TrackerLink.Location.Timestamp)
  440. .Add(x=>x.TrackerLink.Location.Address)
  441. .Add(x=>x.TrackerLink.DeviceID)
  442. .Add(x=>x.TrackerLink.BatteryLevel)
  443. );
  444. query.Query( _ =>
  445. {
  446. Dispatcher.BeginInvoke(() =>
  447. {
  448. bRefreshing = true;
  449. foreach (var row in query.Get<Document>().Rows)
  450. {
  451. var img = ImageUtils.LoadImage(row.Get<Document, byte[]>(x => x.Data));
  452. EquipmentThumbnailConverter.Cache[row.Get<Document, Guid>(x => x.ID)] = img;
  453. }
  454. // EquipmentThumbnailConverter.Cache = query.Get<Document>()
  455. // .ToDictionary<Document, Guid, BitmapImage?>(x => x.ID,
  456. // x => ImageUtils.LoadImage(x.Data));
  457. Sites = query.Get<GeoFence>().ToArray<GeoFence>();
  458. SelectedSiteType = SiteTypes.FirstOrDefault(x => x.Item1 == Settings.GeofenceType) ?? SiteTypes.First();
  459. SelectedSite = null;
  460. var groups = query.Get<EquipmentGroup>().ToList<EquipmentGroup>();
  461. groups.Insert(0,new EquipmentGroup() { ID = CoreUtils.FullGuid, Description="(All Groups)"});
  462. EquipmentGroups = groups.ToArray();
  463. SelectedEquipmentGroup = EquipmentGroups?.FirstOrDefault(x=>x.ID == Settings.GroupID);
  464. Equipment = query.Get<Equipment>().ToArray<Equipment>();
  465. SelectedEquipment = null;
  466. bRefreshing = false;
  467. ReloadLocations();
  468. });
  469. });
  470. }
  471. public event PropertyChangedEventHandler? PropertyChanged;
  472. protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
  473. {
  474. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  475. }
  476. protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
  477. {
  478. if (EqualityComparer<T>.Default.Equals(field, value)) return false;
  479. field = value;
  480. OnPropertyChanged(propertyName);
  481. return true;
  482. }
  483. }