MapViewModel.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Drawing;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using System.Windows;
  8. using System.Windows.Threading;
  9. using Comal.Classes;
  10. using InABox.Clients;
  11. using InABox.Core;
  12. using Inflector;
  13. using Syncfusion.UI.Xaml.Maps;
  14. using Point = System.Windows.Point;
  15. namespace PRSDesktop;
  16. public enum LiveMapView
  17. {
  18. All,
  19. Selected
  20. }
  21. public class MapViewModel : DependencyObject
  22. {
  23. static readonly DependencyProperty ViewProperty =
  24. DependencyProperty.Register(
  25. nameof(View),
  26. typeof(LiveMapView),
  27. typeof(MapViewModel),
  28. new PropertyMetadata(LiveMapView.All)
  29. );
  30. public LiveMapView View
  31. {
  32. get => (LiveMapView)GetValue(ViewProperty);
  33. set
  34. {
  35. SetValue(ViewProperty, value);
  36. Refresh();
  37. }
  38. }
  39. static readonly DependencyProperty StyleProperty =
  40. DependencyProperty.Register(
  41. nameof(Style),
  42. typeof(LiveMapStyle),
  43. typeof(MapViewModel),
  44. new PropertyMetadata(LiveMapStyle.OSM)
  45. );
  46. public LiveMapStyle Style
  47. {
  48. get => (LiveMapStyle)GetValue(StyleProperty);
  49. set => SetValue(StyleProperty, value);
  50. }
  51. static readonly DependencyProperty ApiKeyProperty =
  52. DependencyProperty.Register(
  53. nameof(ApiKey),
  54. typeof(string),
  55. typeof(MapViewModel),
  56. new PropertyMetadata("")
  57. );
  58. public string ApiKey
  59. {
  60. get => (string)GetValue(ApiKeyProperty);
  61. set => SetValue(ApiKeyProperty, value);
  62. }
  63. static readonly DependencyProperty LayersProperty =
  64. DependencyProperty.Register(
  65. nameof(Layers),
  66. typeof(ObservableCollection<ImageryLayer>),
  67. typeof(MapViewModel),
  68. new PropertyMetadata(new ObservableCollection<ImageryLayer>())
  69. );
  70. public ObservableCollection<ImageryLayer> Layers => (ObservableCollection<ImageryLayer>)GetValue(LayersProperty);
  71. static readonly DependencyProperty EntityTypesProperty =
  72. DependencyProperty.Register(
  73. nameof(EntityTypes),
  74. typeof(Dictionary<Type, String>),
  75. typeof(MapViewModel),
  76. new PropertyMetadata(new Dictionary<Type, String>())
  77. );
  78. public Dictionary<Type, String> EntityTypes => (Dictionary<Type, String>)GetValue(EntityTypesProperty);
  79. static readonly DependencyProperty EntityTypeProperty =
  80. DependencyProperty.Register(
  81. nameof(EntityType),
  82. typeof(Type),
  83. typeof(MapViewModel)
  84. );
  85. public Type? EntityType
  86. {
  87. get => (Type) GetValue(EntityTypeProperty);
  88. set
  89. {
  90. SetValue(EntityTypesProperty, value);
  91. ResetGroups();
  92. }
  93. }
  94. static readonly DependencyProperty EntityGroupsProperty =
  95. DependencyProperty.Register(
  96. nameof(EntityGroups),
  97. typeof(Dictionary<Guid, String>),
  98. typeof(MapViewModel),
  99. new PropertyMetadata(new Dictionary<Guid, String>())
  100. );
  101. public Dictionary<Guid, String> EntityGroups => (Dictionary<Guid, String>)GetValue(EntityGroupsProperty);
  102. static readonly DependencyProperty EntityGroupProperty =
  103. DependencyProperty.Register(
  104. nameof(EntityGroup),
  105. typeof(Guid),
  106. typeof(MapViewModel),
  107. new PropertyMetadata(Guid.Empty)
  108. );
  109. public Guid EntityGroup
  110. {
  111. get => (Guid)GetValue(EntityGroupProperty);
  112. set
  113. {
  114. SetValue(EntityGroupProperty,value);
  115. Refresh();
  116. }
  117. }
  118. //private readonly List<MapMarker> _markers = new();
  119. //private readonly List<MapMarker> _waypoints = new();
  120. //private MapMarker? _selected;
  121. static readonly DependencyProperty MarkersProperty =
  122. DependencyProperty.Register(
  123. nameof(Markers),
  124. typeof(ObservableCollection<MapMarker>),
  125. typeof(MapViewModel),
  126. new PropertyMetadata(new ObservableCollection<MapMarker>())
  127. );
  128. public ObservableCollection<MapMarker> Markers => (ObservableCollection<MapMarker>)GetValue(MarkersProperty);
  129. static readonly DependencyProperty SelectedMarkerProperty =
  130. DependencyProperty.Register(
  131. nameof(SelectedMarker),
  132. typeof(MapMarker),
  133. typeof(MapViewModel)
  134. );
  135. public MapMarker? SelectedMarker
  136. {
  137. get => GetValue(SelectedMarkerProperty) as MapMarker;
  138. set => SetValue(SelectedMarkerProperty, value);
  139. }
  140. static readonly DependencyProperty WayPointsProperty =
  141. DependencyProperty.Register(
  142. nameof(WayPoints),
  143. typeof(ObservableCollection<PointF>),
  144. typeof(MapViewModel),
  145. new PropertyMetadata(new ObservableCollection<PointF>())
  146. );
  147. public ObservableCollection<PointF> WayPoints => (ObservableCollection<PointF>)GetValue(WayPointsProperty);
  148. private LiveMapsSettings? _settings;
  149. public void Setup(LiveMapsSettings settings)
  150. {
  151. _settings = settings;
  152. LoadEntityTypes();
  153. EntityType = !String.IsNullOrWhiteSpace(_settings.EntityType)
  154. ? EntityTypes.Any(x => String.Equals(x.Key.EntityName(), _settings.EntityType))
  155. ? Type.GetType(_settings.EntityType)
  156. : null
  157. : null;
  158. EntityGroup = EntityGroups.Any(x => String.Equals(x.Key, _settings.EntityGroup))
  159. ? _settings.EntityGroup
  160. : Guid.Empty;
  161. Style = _settings.MapStyle;
  162. ApiKey = _settings.ApiKey;
  163. }
  164. private void LoadEntityTypes()
  165. {
  166. if (ClientFactory.IsSupported<Equipment>())
  167. EntityTypes[typeof(Equipment)] = typeof(Equipment).GetCaption().Pluralize();
  168. if (ClientFactory.IsSupported<Job>())
  169. EntityTypes[typeof(Job)] = typeof(Job).GetCaption().Pluralize();
  170. if (ClientFactory.IsSupported<TimeSheet>())
  171. EntityTypes[typeof(TimeSheet)] = typeof(TimeSheet).GetCaption().Pluralize();
  172. if (ClientFactory.IsSupported<GPSTracker>())
  173. EntityTypes[typeof(GPSTracker)] = typeof(GPSTracker).GetCaption().Pluralize();
  174. }
  175. private void ResetGroups()
  176. {
  177. EntityGroups.Clear();
  178. if (Type.Equals(EntityType,typeof(Equipment)))
  179. LoadGroups<EquipmentGroup>("All Equipment", x => x.Description);
  180. else if (Type.Equals(EntityType,typeof(Job)))
  181. LoadGroups<JobStatus>("All Jobs", x => x.Description);
  182. else if (Type.Equals(EntityType,typeof(GPSTracker)))
  183. LoadGroups<GPSTrackerType>("All Trackers", x => x.Description);
  184. else if (Type.Equals(EntityType,typeof(TimeSheet)))
  185. {
  186. EntityGroups[Guid.Empty] = "Open TimeSheets";
  187. EntityGroups[Guid.NewGuid()] = "TimeSheet Starts";
  188. EntityGroups[CoreUtils.FullGuid] = "TimeSheet Finishes";
  189. }
  190. }
  191. private void LoadGroups<TGroup>(string all, Expression<Func<TGroup, string>> description)
  192. where TGroup : Entity, IRemotable, IPersistent, new()
  193. {
  194. EntityGroups.Add(Guid.Empty, all);
  195. var _groups = new Client<TGroup>().Query(
  196. LookupFactory.DefineFilter<TGroup>(),
  197. LookupFactory.DefineColumns<TGroup>(),
  198. LookupFactory.DefineSort<TGroup>()
  199. );
  200. foreach (var _row in _groups.Rows)
  201. EntityGroups[_row.Get<TGroup, Guid>(x => x.ID)] = _row.Get(description);
  202. }
  203. public void Refresh()
  204. {
  205. Markers.Clear();
  206. WayPoints.Clear();
  207. if (View == LiveMapView.All)
  208. LoadMarkers();
  209. else if (SelectedMarker != null)
  210. {
  211. LoadWayPoints();
  212. foreach (var waypoint in _waypoints)
  213. {
  214. Markers.Add(waypoint);
  215. WayPoints.Add(new Point(double.Parse(waypoint.Longitude), double.Parse(waypoint.Latitude)));
  216. }
  217. }
  218. }
  219. public void LoadMarkers()
  220. {
  221. if (EntityType != null)
  222. {
  223. if (EntityType.Equals("Equipment"))
  224. {
  225. var filter = new Filter<Equipment>(x => x.TrackerLink.Location.Timestamp).IsNotEqualTo(DateTime.MinValue);
  226. if (!Security.IsAllowed<CanViewPrivateEquipment>())
  227. filter = filter.And(x => x.Private).IsEqualTo(false);
  228. if (EntityGroup != Guid.Empty)
  229. filter = filter.And(x => x.GroupLink.ID).IsEqualTo(EntityGroup);
  230. LoadMarkers(
  231. filter,
  232. x => x.ID,
  233. x => x.Code,
  234. x => x.Description,
  235. x => x.TrackerLink.DeviceID,
  236. x => x.TrackerLink.Location.Latitude,
  237. x => x.TrackerLink.Location.Longitude,
  238. x => x.TrackerLink.Description,
  239. x => x.TrackerLink.Location.Timestamp
  240. );
  241. }
  242. else if (EntityType.Equals("Jobs"))
  243. {
  244. var filter = new Filter<Job>(x => x.SiteAddress.Location.Timestamp).IsNotEqualTo(DateTime.MinValue);
  245. if (EntityGroup != Guid.Empty)
  246. filter = filter.And(x => x.JobStatus.ID).IsEqualTo(EntityGroup);
  247. LoadMarkers(
  248. filter,
  249. x => x.ID,
  250. x => x.JobNumber,
  251. x => x.Name,
  252. null,
  253. x => x.SiteAddress.Location.Latitude,
  254. x => x.SiteAddress.Location.Longitude,
  255. x => x.LastUpdateBy,
  256. x => x.SiteAddress.Location.Timestamp
  257. );
  258. }
  259. else if (EntityType.Equals("TimeSheets"))
  260. {
  261. if (EntityGroup == Guid.Empty) // Starts
  262. LoadMarkers(
  263. new Filter<TimeSheet>(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.StartLocation.Timestamp)
  264. .IsNotEqualTo(new TimeSpan(0)),
  265. x => x.EmployeeLink.ID,
  266. x => x.EmployeeLink.Code,
  267. x => x.EmployeeLink.Name,
  268. null,
  269. x => x.StartLocation.Latitude,
  270. x => x.StartLocation.Longitude,
  271. x => x.SoftwareVersion,
  272. x => x.StartLocation.Timestamp
  273. );
  274. else if (EntityGroup == CoreUtils.FullGuid) // Finishes
  275. LoadMarkers(
  276. new Filter<TimeSheet>(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.Finish).IsNotEqualTo(new TimeSpan(0))
  277. .And(x => x.FinishLocation.Timestamp).IsNotEqualTo(new TimeSpan(0)),
  278. x => x.EmployeeLink.ID,
  279. x => x.EmployeeLink.Code,
  280. x => x.EmployeeLink.Name,
  281. null,
  282. x => x.FinishLocation.Latitude,
  283. x => x.FinishLocation.Longitude,
  284. x => x.SoftwareVersion,
  285. x => x.FinishLocation.Timestamp
  286. );
  287. else // Open
  288. LoadMarkers(
  289. new Filter<TimeSheet>(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.Finish).IsEqualTo(new TimeSpan(0))
  290. .And(x => x.StartLocation.Timestamp).IsNotEqualTo(new TimeSpan(0)),
  291. x => x.EmployeeLink.ID,
  292. x => x.EmployeeLink.Code,
  293. x => x.EmployeeLink.Name,
  294. null,
  295. x => x.StartLocation.Latitude,
  296. x => x.StartLocation.Longitude,
  297. x => x.SoftwareVersion,
  298. x => x.StartLocation.Timestamp
  299. );
  300. }
  301. else if (EntityType.Equals("Trackers"))
  302. {
  303. var filter = new Filter<GPSTracker>(x => x.Location.Timestamp).IsNotEqualTo(DateTime.MinValue);
  304. if (EntityGroup != Guid.Empty)
  305. filter = filter.And(x => x.Type.ID).IsEqualTo(EntityGroup);
  306. LoadMarkers(
  307. filter,
  308. x => x.ID,
  309. x => x.DeviceID,
  310. x => x.Description,
  311. x => x.DeviceID,
  312. x => x.Location.Latitude,
  313. x => x.Location.Longitude,
  314. x => x.LastUpdateBy,
  315. x => x.Location.Timestamp
  316. );
  317. }
  318. }
  319. }
  320. private class DbMarker
  321. {
  322. public DbMarker(string code, string description, DateTime timestamp, double latitude, double longitude, string updatedby, string deviceid)
  323. {
  324. Code = code;
  325. Description = description;
  326. TimeStamp = timestamp;
  327. Latitude = latitude;
  328. Longitude = longitude;
  329. UpdatedBy = updatedby;
  330. DeviceID = deviceid;
  331. }
  332. public string Code { get; }
  333. public string Description { get; }
  334. public DateTime TimeStamp { get; }
  335. public double Latitude { get; }
  336. public double Longitude { get; }
  337. public string UpdatedBy { get; }
  338. public string DeviceID { get; }
  339. }
  340. private void LoadMarkers<T>(Filter<T> filter, Expression<Func<T, Guid>> guid, Expression<Func<T, string>>? code,
  341. Expression<Func<T, string>> description, Expression<Func<T, string>>? deviceid, Expression<Func<T, double>> latitude,
  342. Expression<Func<T, double>> longitude, Expression<Func<T, string>> updatedby, Expression<Func<T, DateTime>> timestamp, bool first = true)
  343. where T : Entity, IRemotable, IPersistent, new()
  344. {
  345. var _columns = new Columns<T>(x=>x.ID)
  346. .Add(guid)
  347. .Add(description)
  348. .Add(latitude)
  349. .Add(longitude)
  350. .Add(timestamp)
  351. .Add(updatedby);
  352. if (code != null)
  353. _columns.Add(code);
  354. if (deviceid != null && !_columns.ColumnNames().Contains(CoreUtils.GetFullPropertyName(deviceid, ".")))
  355. _columns.Add(deviceid);
  356. new Client<T>().Query(
  357. filter,
  358. _columns,
  359. null,
  360. (table, error) =>
  361. {
  362. Dispatcher.Invoke(() =>
  363. {
  364. if (error != null)
  365. {
  366. MessageBox.Show(error.Message);
  367. }
  368. else if (table != null)
  369. {
  370. var _values = new Dictionary<Guid, DbMarker>();
  371. var _groups = table.Rows
  372. .OrderBy(x=>x.Get(code ?? description))
  373. .ThenBy(x=>x.Get(timestamp))
  374. .Select(x => x.ToObject<T>())
  375. .GroupBy(x => x.ID);
  376. foreach (var _group in _groups)
  377. {
  378. var last = _group.OrderBy(timefunc).LastOrDefault();
  379. if (last != null)
  380. Markers.Add(
  381. new MapMarker
  382. {
  383. ID = last.ID,
  384. Label = last.,
  385. Latitude = string.Format("{0:F6}", _values[key].Latitude),
  386. Longitude = string.Format("{0:F6}", _values[key].Longitude),
  387. Description = _values[key].Description,
  388. Updated = _values[key].TimeStamp,
  389. UpdatedBy = _values[key].UpdatedBy,
  390. DeviceID = _values[key].DeviceID
  391. }
  392. );
  393. }
  394. foreach (var _row in table.Rows)
  395. {
  396. var _id = _row.Get(guid);
  397. var _time = _row.Get(timestamp);
  398. if (!_values.ContainsKey(_id) || (first ? _values[_id].TimeStamp < _time : _values[_id].TimeStamp > _time))
  399. _values[_id] = new DbMarker(
  400. code != null ? _row.Get(code) : "",
  401. _row.Get(description),
  402. _time,
  403. _row.Get(latitude),
  404. _row.Get(longitude),
  405. _row.Get(updatedby),
  406. deviceid != null ? _row.Get(deviceid) : ""
  407. );
  408. }
  409. foreach (var key in _values.Keys)
  410. Markers.Add(
  411. new MapMarker
  412. {
  413. ID = key,
  414. Label = _values[key].Code,
  415. Latitude = string.Format("{0:F6}", _values[key].Latitude),
  416. Longitude = string.Format("{0:F6}", _values[key].Longitude),
  417. Description = _values[key].Description,
  418. Updated = _values[key].TimeStamp,
  419. UpdatedBy = _values[key].UpdatedBy,
  420. DeviceID = _values[key].DeviceID
  421. }
  422. );
  423. }
  424. ResetZoom();
  425. LoadMarkerList();
  426. });
  427. }
  428. );
  429. }
  430. private void ResetZoom()
  431. {
  432. var nwLon = double.MaxValue;
  433. var nwLat = double.MinValue;
  434. var seLon = double.MinValue;
  435. var seLat = double.MaxValue;
  436. foreach (var marker in _markers)
  437. {
  438. var lat = double.Parse(marker.Latitude);
  439. var lon = double.Parse(marker.Longitude);
  440. if (lat != 0.0F && lon != 0.00)
  441. {
  442. nwLat = lat > nwLat ? lat : nwLat;
  443. seLat = lat < seLat ? lat : seLat;
  444. nwLon = lon < nwLon ? lon : nwLon;
  445. seLon = lon > seLon ? lon : seLon;
  446. }
  447. }
  448. var cLat = (nwLat + seLat) / 2.0F;
  449. var cLon = (nwLon + seLon) / 2.0F;
  450. Center = new Point(cLat, cLon);
  451. var nw = new Location { Latitude = nwLat, Longitude = nwLon };
  452. var c = new Location { Latitude = cLat, Longitude = cLon };
  453. Radius = c.DistanceTo(nw, UnitOfLength.Kilometers) / 2.0F;
  454. }
  455. public double Radius { get; set; }
  456. public Point Center { get; set; }
  457. }