|
@@ -1,12 +1,27 @@
|
|
|
|
+using Avalonia;
|
|
|
|
+using Comal.Classes;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using InABox.Avalonia;
|
|
using InABox.Avalonia;
|
|
using InABox.Avalonia.Components;
|
|
using InABox.Avalonia.Components;
|
|
|
|
+using InABox.Configuration;
|
|
|
|
+using InABox.Core;
|
|
using PRS.Avalonia.Dialogs;
|
|
using PRS.Avalonia.Dialogs;
|
|
|
|
+using ReactiveUI;
|
|
|
|
+using System;
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
+using System.Collections.ObjectModel;
|
|
|
|
+using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
|
|
+using static PRS.Avalonia.Modules.EquipmentMapsView;
|
|
|
|
|
|
namespace PRS.Avalonia.Modules;
|
|
namespace PRS.Avalonia.Modules;
|
|
|
|
|
|
|
|
+public class EquipmentMapsSettings : ILocalConfigurationSettings
|
|
|
|
+{
|
|
|
|
+ public Guid[] SelectedCategories { get; set; } = [];
|
|
|
|
+}
|
|
|
|
+
|
|
public partial class EquipmentMapsViewModel : ModuleViewModel
|
|
public partial class EquipmentMapsViewModel : ModuleViewModel
|
|
{
|
|
{
|
|
public override string Title => "Live Maps";
|
|
public override string Title => "Live Maps";
|
|
@@ -14,19 +29,179 @@ public partial class EquipmentMapsViewModel : ModuleViewModel
|
|
[ObservableProperty]
|
|
[ObservableProperty]
|
|
private string _searchText = "";
|
|
private string _searchText = "";
|
|
|
|
|
|
|
|
+ private readonly EquipmentMapsSettings _settings;
|
|
|
|
+
|
|
|
|
+ [ObservableProperty]
|
|
|
|
+ private ObservableCollection<Marker> _jobMarkers = new();
|
|
|
|
+
|
|
|
|
+ [ObservableProperty]
|
|
|
|
+ private ObservableCollection<Marker> _equipmentMarkers = new();
|
|
|
|
+
|
|
|
|
+ [ObservableProperty]
|
|
|
|
+ private JobModel _jobs;
|
|
|
|
+
|
|
|
|
+ [ObservableProperty]
|
|
|
|
+ private EquipmentModel _equipment;
|
|
|
|
+
|
|
|
|
+ [ObservableProperty]
|
|
|
|
+ private EquipmentGroupModel _equipmentGroups;
|
|
|
|
+
|
|
|
|
+ [ObservableProperty]
|
|
|
|
+ private Point _coordinates;
|
|
|
|
+
|
|
|
|
+ [ObservableProperty]
|
|
|
|
+ private int _zoomLevel;
|
|
|
|
+
|
|
public EquipmentMapsViewModel()
|
|
public EquipmentMapsViewModel()
|
|
{
|
|
{
|
|
|
|
+ _settings = new LocalConfiguration<EquipmentMapsSettings>().Load();
|
|
|
|
+
|
|
|
|
+ Jobs = new JobModel(
|
|
|
|
+ DataAccess,
|
|
|
|
+ () => Repositories.JobFilter(),
|
|
|
|
+ () => DefaultCacheFileName<JobShell>());
|
|
|
|
+
|
|
|
|
+ Equipment = new EquipmentModel(
|
|
|
|
+ DataAccess,
|
|
|
|
+ () => new Filter<Equipment>().All(),
|
|
|
|
+ () => DefaultCacheFileName<EquipmentShell>());
|
|
|
|
+
|
|
|
|
+ EquipmentGroups = new EquipmentGroupModel(
|
|
|
|
+ DataAccess,
|
|
|
|
+ () => LookupFactory.DefineFilter<EquipmentGroup>());
|
|
|
|
+
|
|
PrimaryMenu.Add(new AvaloniaMenuItem(Images.menu, SelectFilter));
|
|
PrimaryMenu.Add(new AvaloniaMenuItem(Images.menu, SelectFilter));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ protected override async Task<TimeSpan> OnRefresh()
|
|
|
|
+ {
|
|
|
|
+ await Task.WhenAll(
|
|
|
|
+ Jobs.RefreshAsync(false),
|
|
|
|
+ Equipment.RefreshAsync(false),
|
|
|
|
+ EquipmentGroups.RefreshAsync(false));
|
|
|
|
+
|
|
|
|
+ Refresh();
|
|
|
|
+
|
|
|
|
+ return TimeSpan.Zero;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void Refresh()
|
|
|
|
+ {
|
|
|
|
+ var jobs = Jobs.Items.Where(FilterJob)
|
|
|
|
+ .Select(x => new EquipmentMapsView.Marker(x.JobNumber, x.Location.Latitude, x.Location.Longitude))
|
|
|
|
+ .ToArray();
|
|
|
|
+ var equipment = Equipment.Items.Where(FilterEquipment)
|
|
|
|
+ .Select(x => new EquipmentMapsView.Marker(x.Code, x.Latitude, x.Longitude))
|
|
|
|
+ .ToArray();
|
|
|
|
+
|
|
|
|
+ CenterAndZoom(jobs.Concat(equipment).Select(x => x.Coordinates).ToArray());
|
|
|
|
+
|
|
|
|
+ JobMarkers = new ObservableCollection<Marker>(jobs);
|
|
|
|
+ EquipmentMarkers = new ObservableCollection<Marker>(equipment);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void CenterAndZoom(Point[] points)
|
|
|
|
+ {
|
|
|
|
+ points = points.Where(x => x != default).ToArray();
|
|
|
|
+
|
|
|
|
+ Point coords;
|
|
|
|
+ int zoom = 15;
|
|
|
|
+
|
|
|
|
+ if (points.Length == 0)
|
|
|
|
+ {
|
|
|
|
+ if(App.GPS is not null)
|
|
|
|
+ {
|
|
|
|
+ coords = new Point(App.GPS.Latitude, App.GPS.Longitude);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (points.Length == 1)
|
|
|
|
+ {
|
|
|
|
+ coords = new Point(points[0].Y, points[0].X);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ var latitudes = points.Select(x => x.Y).OrderBy(x => x).ToList();
|
|
|
|
+ var longitudes = points.Select(x => x.X).OrderBy(x => x).ToList();
|
|
|
|
+
|
|
|
|
+ var firstLat = latitudes.First();
|
|
|
|
+ var lastLat = latitudes.Last();
|
|
|
|
+ var firstLong = longitudes.First();
|
|
|
|
+ var lastLong = longitudes.Last();
|
|
|
|
+ var resultLat = (firstLat + lastLat) / 2;
|
|
|
|
+ var resultLong = (firstLong + lastLong) / 2;
|
|
|
|
+
|
|
|
|
+ var firstLocation = new Location()
|
|
|
|
+ { Latitude = firstLat, Longitude = firstLong };
|
|
|
|
+ var lastLocation = new Location()
|
|
|
|
+ { Latitude = lastLat, Longitude = lastLong };
|
|
|
|
+ var distance = firstLocation.DistanceTo(lastLocation, UnitOfLength.Kilometers);
|
|
|
|
+ coords = new Point(resultLat, resultLong);
|
|
|
|
+ zoom = CalculateZoom(distance);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Coordinates = coords;
|
|
|
|
+ ZoomLevel = zoom;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static int CalculateZoom(double distance)
|
|
|
|
+ {
|
|
|
|
+ Dictionary<double, int> thresholds = new Dictionary<double, int>()
|
|
|
|
+ {
|
|
|
|
+ { 1, 17 },
|
|
|
|
+ { 5, 16 },
|
|
|
|
+ { 10, 15 },
|
|
|
|
+ { 20, 11 },
|
|
|
|
+ { 50, 10 },
|
|
|
|
+ { 100, 9 },
|
|
|
|
+ { 200, 8 },
|
|
|
|
+ { 400, 7 },
|
|
|
|
+ };
|
|
|
|
+ foreach (var key in thresholds.Keys.OrderBy(x => x))
|
|
|
|
+ {
|
|
|
|
+ if (distance < key)
|
|
|
|
+ return thresholds[key];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 6;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private bool FilterJob(JobShell shell)
|
|
|
|
+ {
|
|
|
|
+ return _settings.SelectedCategories.Contains(CoreUtils.FullGuid)
|
|
|
|
+ && (shell.Location.Latitude != 0.0F) && (shell.Location.Longitude != 0.0F)
|
|
|
|
+ && (
|
|
|
|
+ SearchText.IsNullOrWhiteSpace()
|
|
|
|
+ || shell.JobNumber.Contains(SearchText, StringComparison.InvariantCultureIgnoreCase)
|
|
|
|
+ || shell.Name.Contains(SearchText, StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
+ }
|
|
|
|
+ private bool FilterEquipment(EquipmentShell shell)
|
|
|
|
+ {
|
|
|
|
+ return _settings.SelectedCategories.Contains(shell.GroupID)
|
|
|
|
+ && (shell.Latitude != 0.0F) && (shell.Longitude != 0.0F)
|
|
|
|
+ && (
|
|
|
|
+ SearchText.IsNullOrWhiteSpace()
|
|
|
|
+ || shell.Code.Contains(SearchText, StringComparison.InvariantCultureIgnoreCase)
|
|
|
|
+ || shell.Description.Contains(SearchText, StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
+ }
|
|
|
|
+
|
|
private async Task<bool> SelectFilter()
|
|
private async Task<bool> SelectFilter()
|
|
{
|
|
{
|
|
- return true;
|
|
|
|
|
|
+ return await Task.FromResult(true);
|
|
}
|
|
}
|
|
|
|
|
|
[RelayCommand]
|
|
[RelayCommand]
|
|
private void Search()
|
|
private void Search()
|
|
{
|
|
{
|
|
|
|
+ Refresh();
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ [RelayCommand]
|
|
|
|
+ private void Reset()
|
|
|
|
+ {
|
|
|
|
+ CenterAndZoom(JobMarkers.Concat(EquipmentMarkers).Select(x => x.Coordinates).ToArray());
|
|
}
|
|
}
|
|
}
|
|
}
|