using System; using System.Collections.Generic; using SafeMobileLib; using Safedispatch_4_0; using System.Threading.Tasks; using System.Threading; using System.Text; using System.Windows.Forms; using System.Linq; namespace Dispatcher.maptab { /// /// - MapHashName : is the Live Tab instance name (each instance has an unique name) in order to find the state of an unit on that tab /// - HasLabels : provide indicator if the markers will have labels or they will be hidden /// - IsMetric : speed unit will be set to metric if true, otherwise it will be in miles /// - IsConsoleVisible : provides indicator if the console needs to be displayed for debug reasons /// - IconType : pass the style of units that is used in order to change the labels position and color accordingly /// - Center Position : default position to which the map needs to be center at initialization(usualy is the last visible position) /// - HideTimeInactiveUnits : indicator if the units needs to be removed from the map if the GPS is older than a predefined amount of minutes /// - HideNonEmergUnits : indicator if the units need to be removed from the map if they are not in emergency /// - InactiveTimeutMinutes : number of minutes after which a unit can be considere inactive /// - IsNeverReportedWaning : raise events which time you try to display a unit that had never reported any gps /// - MaxDisplayedNrOfUnits : set dataset limit after which the units will be skipped by the map js library /// public partial class MapHandler : UserControl, IMap { private String mapUrl = ""; private int NeverReportedTime = 1262304000; private int GPSHandleIntervalSec = 7; private readonly int TimeoutBetweenCmdMs = 700; private readonly bool SEQ_ID_REQ = false; private readonly String UNASSIGNED = "unASSigned"; #region Map commands private readonly string ShowConsole = "showconsole"; private readonly string SetLegacyIcon = "setLegacyIcons"; private readonly string CenterZoom = "centerzoom"; private readonly string SetDatasetLimit = "setdatasetlimit"; private readonly string UpdateDataSet = "updatedataset"; private readonly string RemoveDataSet = "removedataset"; private readonly string DeleteFromDataset = "deletefromdataset"; private readonly string PutLabels = "putlabels"; private readonly string RemoveLabels = "removelabels"; private readonly string PutOnMap = "putOnMap"; private readonly string OpenInfo = "openInfo"; private readonly string MapLoaded = "map-loaded"; private readonly string Nearest = "nearest"; private readonly string UnitDisplayReq = "request-unit-display"; private readonly string UpdateTime = "exec-time"; private readonly string FastCommand = "fast-command"; private readonly string TextCommand = "text-command"; private readonly string PTTCommand = "ptt-command"; private readonly string StreetViewOpened = "street-view-open"; private readonly string Landmark = "landmark"; private readonly string ShowLandmark = "updatedataset"; private readonly string ShowGeofence = "setPolygon"; private readonly string DeleteGeofence = "deletePolygon"; private readonly string Done = "done"; private readonly string Bounds = "bounds"; #endregion private CefSharpChromiumWebBrowser mapGoogles; private Dictionary InactiveScIdsVehicle = new Dictionary(); private Dictionary displayedScIdsVehicle = new Dictionary(); private Dictionary newGPSScIdsVehicle = new Dictionary(); private List dispalyedDatasets = new List(); private Int64 SeqId = 1; private bool hideTimeInactiveUnits = false; private bool showEmergencyForTimeInactiveUnits = true; private bool hideNonEmergUnits = false; private int inactiveTimeoutMinutes = Int32.MaxValue; private bool isNeverReportedWarning = true; private bool isConsoleVisible = false; private IconTheme iconTheme = IconTheme.PIN; private IconType iconType = IconType.ARROWS; private LatLngZoom centerPosition = new LatLngZoom(); private Int32 maxDisplayedNrOfUnits = 20000; private bool isMetric = true; private bool hasLabels = true; private int datasetChunkSize = 70; private int lastResponseMs = 0; private bool mapDead = false; private Int32 TimeoutMapDeadMs = 5000; private Object mapLock = new Object(); #region SETTERS AND GETTERS private String mapHashName; public string MapHashName { get { return mapHashName; } set { mapHashName = value; } } public bool ConsoleVisible { get { return isConsoleVisible; } set { isConsoleVisible = value; ChangeConsoleState(isConsoleVisible); //(this as IMap).changeConsoleState(value); } } public bool HideNonEmergUnits { get { return hideNonEmergUnits; } set { hideNonEmergUnits = value; } } public bool HideTimeInactiveUnits { get { return hideTimeInactiveUnits; } set { hideTimeInactiveUnits = value; } } public bool ShowEmergencyForTimeInactiveUnits { get { return showEmergencyForTimeInactiveUnits; } set { showEmergencyForTimeInactiveUnits = value; } } public bool IsNeverReportedWarning { get { return isNeverReportedWarning; } set { isNeverReportedWarning = value; } } public int InactiveTimeoutMinutes { get { return inactiveTimeoutMinutes; } set { inactiveTimeoutMinutes = value; } } public IconTheme IconTheme { get { return iconTheme; } set { iconTheme = value; //(this as IMap).changeMarkersType(value); } } public IconType IconType { get { return iconType; } set { iconType = value; //(this as IMap).changeMarkersType(value); } } public LatLngZoom CenterPosition { get { return centerPosition; } set { centerPosition = value; } } public int MaxDisplayedNrOfUnits { get { return maxDisplayedNrOfUnits; } set { maxDisplayedNrOfUnits = value; } } public bool IsMetric { get { return isMetric; } set { isMetric = value; } } public bool HasLabels { get { return hasLabels; } set { hasLabels = value; } } #endregion // thread lock so only one update will have access to the map at a time private object _lock = 0; private long numberOfRunningTasks = 0; private bool mapLoaded = false; // Create a semaphore that can satisfy up to three // concurrent requests. Use an initial count of zero, // so that the entire semaphore count is initially // owned by the main program thread. // private Semaphore semaphore = new Semaphore(0, 1); private int semaphoreTimeoutMs = 2000; public MapHandler(String url) { InitializeComponent(); mapUrl = url; //this.Dock = DockStyle.Fill; // create a task taht will handle any map changes Task.Factory.StartNew(() => MapWorker()); // initialize the map control InitializeMap(); } /// /// Initialize the map by creating the control and adding event handlers /// private void InitializeMap() { mapGoogles = new CefSharpChromiumWebBrowser(mapUrl); mapGoogles.Dock = DockStyle.Fill; // create event handler for when the map is loaded mapGoogles.OnWebPageLoaded += OnWePageLoadedHandler; mapGoogles.OnURLResponseReceived += OnMapMessageReceivedHandler; // add map control to the mai form controll this.Controls.Add(mapGoogles); } /// /// Restart the map by removing the previous control and handlers /// and the by initializing it again /// public void RestartMap() { // remove previous handlers if (mapGoogles != null) { this.Controls.Remove(mapGoogles); mapGoogles.OnWebPageLoaded -= OnWePageLoadedHandler; mapGoogles.OnURLResponseReceived -= OnMapMessageReceivedHandler; mapGoogles.Dispose(); } // reinitialize the map InitializeMap(); } /// /// Handle all map request as of unit removing and adding, position updates and icon updates /// private void MapWorker() { int countInactive = 0; int count = 0; while (MainForm2.isRunning) { Thread.Sleep(100); //Task.WaitAll if (count++ > GPSHandleIntervalSec * 10 && mapLoaded) { count = 0; long runningTasks = Interlocked.Read(ref numberOfRunningTasks); Utils.WriteLine("Hash count active: " + displayedScIdsVehicle.Count + " | inactive: " + InactiveScIdsVehicle.Count, ConsoleColor.Cyan); if (runningTasks != 0) continue; lock (mapLock) { if (newGPSScIdsVehicle.Count == 0) continue; else if (newGPSScIdsVehicle.Count < 100) GPSHandleIntervalSec = 5; else if (newGPSScIdsVehicle.Count < 300) GPSHandleIntervalSec = 15; else if (newGPSScIdsVehicle.Count < 500) GPSHandleIntervalSec = 30; else GPSHandleIntervalSec = 50; List displayedWithGPS = new List(); lock (InactiveScIdsVehicle) { lock (displayedScIdsVehicle) { displayedWithGPS = newGPSScIdsVehicle.Values.ToList() .Where(d => (displayedScIdsVehicle.Values.ToList().Select(w => w.sc_id)).Contains(d.sc_id)).ToList(); Utils.WriteLine("GPS updates for " + displayedWithGPS.Count + " displayed units", ConsoleColor.Yellow); // hide units that are inactive if (HideTimeInactiveUnits || HideNonEmergUnits) { List displayFromInactiveListGPS = newGPSScIdsVehicle.Values.ToList() .Where(d => (InactiveScIdsVehicle.Values.ToList().Select(w => w.sc_id)).Contains(d.sc_id)).ToList(); // move unit from inactive list to the displayed list foreach (Vehicle veh in displayFromInactiveListGPS) { if (InactiveScIdsVehicle.ContainsKey(veh.sc_id)) InactiveScIdsVehicle.Remove(veh.sc_id); if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); displayedScIdsVehicle.Add(veh.sc_id, veh); } Utils.WriteLine("GPS updates for " + displayFromInactiveListGPS.Count + " inactive units", ConsoleColor.Green); // add units that were reactivated to the display list if (displayFromInactiveListGPS.Count > 0) displayedWithGPS.AddRange(displayFromInactiveListGPS); } } } // update the units on the map if (displayedWithGPS.Count > 0) AddVehicles(displayedWithGPS, false); newGPSScIdsVehicle.Clear(); } } if (HideTimeInactiveUnits || HideNonEmergUnits) { // remove inactive units once at 30 seconds if (countInactive++ > 30 * 10 && mapLoaded) { lock (InactiveScIdsVehicle) { lock (displayedScIdsVehicle) { // detect units that needs to be removed due to innactive int positionTimeNewerThan = (DateTime.UtcNow.AddMinutes(-1 * inactiveTimeoutMinutes)).GetSecondsFromDT(); //int nonEmergUnits = ; List toRemoveInactiveMinutesVehicles; if (showEmergencyForTimeInactiveUnits) toRemoveInactiveMinutesVehicles = displayedScIdsVehicle.Values.ToList().Where(d => d.lastValidPositionTime < positionTimeNewerThan && d.lastValidPositionTime > NeverReportedTime && d.actual_status != Status_for_tab.EMERG).ToList(); else toRemoveInactiveMinutesVehicles = displayedScIdsVehicle.Values.ToList().Where(d => d.lastValidPositionTime < positionTimeNewerThan && d.lastValidPositionTime > NeverReportedTime).ToList(); List toRemoveNonEmergencyVehicles = displayedScIdsVehicle.Values.ToList().Where(d => d.actual_status != Status_for_tab.EMERG).ToList(); if (HideNonEmergUnits) { foreach (Vehicle veh in toRemoveNonEmergencyVehicles) { if (InactiveScIdsVehicle.ContainsKey(veh.sc_id)) InactiveScIdsVehicle.Remove(veh.sc_id); InactiveScIdsVehicle.Add(veh.sc_id, veh); if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); } if (toRemoveNonEmergencyVehicles.Count > 0) { removeVehicles(toRemoveNonEmergencyVehicles, false); if (toRemoveNonEmergencyVehicles.Where(d => d.actual_status != Status_for_tab.NOGPSFIX).ToList().Count > 0) OnInactiveUnitsWarning?.Invoke(toRemoveNonEmergencyVehicles.Count); } } if (HideTimeInactiveUnits) { // add all units to the inactive minutes list foreach (Vehicle veh in toRemoveInactiveMinutesVehicles) { if (InactiveScIdsVehicle.ContainsKey(veh.sc_id)) InactiveScIdsVehicle.Remove(veh.sc_id); InactiveScIdsVehicle.Add(veh.sc_id, veh); if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); } // remove units from the dataset if (toRemoveInactiveMinutesVehicles.Count > 0) { removeVehicles(toRemoveInactiveMinutesVehicles, false); if (toRemoveInactiveMinutesVehicles.Where(d => d.actual_status != Status_for_tab.NOGPSFIX).ToList().Count > 0) OnInactiveUnitsWarning?.Invoke(toRemoveInactiveMinutesVehicles.Count); } } } } } } } } #region MAP HANDLERS /// /// Handler for when a response is received from the map. This is done by a change in the url of the map /// /// The response received from the map private void OnMapMessageReceivedHandler(string response) { if (response.IndexOf("app://") > -1) { //Utils.WriteLine("RESPONSE FROM MAP : " + response, ConsoleColor.Magenta); if (response.IndexOf(MapLoaded) > -1) { mapLoaded = true; ChangeConsoleState(isConsoleVisible); SetDataSetLimit(MaxDisplayedNrOfUnits); // raise the event OnMapLoaded?.Invoke(); } else if(response.IndexOf(Nearest) > -1) { // remove command name and slash String tmpstr = response.Remove(0, "app://".Length + Nearest.Length + 1); tmpstr = tmpstr.Trim(); String[] split = tmpstr.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); Int32 x = 0; Int32 y = 0; Double latitude = 0; Double longitude = 0; Int32.TryParse(split[0], out x); Int32.TryParse(split[1], out y); Double.TryParse(split[2], out latitude); Double.TryParse(split[3], out longitude); // fire event OnNearestRequest?.Invoke(x, y, latitude, longitude); } else if (response.IndexOf(UnitDisplayReq) > -1) { // remove command name and slash String tmpstr = response.Remove(0, "app://".Length + Nearest.Length + 1); tmpstr = tmpstr.Trim(); String[] split = tmpstr.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); // fire event OnUnitDisplayReq?.Invoke(split[1]); } // check response from map else if (response.IndexOf(UpdateTime) > -1) { // remove command name and slash String tmpstr = response.Remove(0, "app://".Length + UpdateTime.Length + 1); tmpstr = tmpstr.Trim(); String[] split = tmpstr.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); // expected two items if (split.Length < 2) return; String cmd = split[0]; int time = 0; Int32.TryParse(split[1], out time); mapDead = false; try { //if(!cmd.StartsWith("set-bounds")) semaphore.Release(); } catch (Exception ex) { Utils.WriteLine("Exception semaphore: " + ex.ToString(), ConsoleColor.Red); } //Utils.WriteLine("semaphore Release for " + cmd.ToUpper(), ConsoleColor.Green); // store the last response lastResponseMs = time; if (time > 1000) datasetChunkSize = (int)(0.8 * datasetChunkSize); else if (time < 500) datasetChunkSize = (int)(1.2 * datasetChunkSize); // prevent chunks smaller that 20 if (datasetChunkSize < 70) datasetChunkSize = 70; else if (datasetChunkSize > 100) datasetChunkSize = 100; } // fast command was pressed else if (response.IndexOf(FastCommand) > -1) { String tmpstr = response.Remove(0, response.IndexOf(FastCommand) + 13); tmpstr = tmpstr.Trim(); } // intercept text command received from the map else if (response.IndexOf(TextCommand) > -1) { String tmpstr = response.Remove(0, response.IndexOf(TextCommand) + 13); tmpstr = tmpstr.Trim(); //string urldecode = HttpServerUtility // get unit name and imei string unitName = tmpstr.Split(new char[] { ',' })[0]; string textMessage = System.Web.HttpUtility.UrlDecode(tmpstr.Split(new char[] { ',' })[1]); // raise the event OnTextMessageRequest?.Invoke(unitName, textMessage); } // intercept ptt command received from the map else if (response.IndexOf(PTTCommand) > -1) { String tmpstr = response.Remove(0, response.IndexOf(PTTCommand) + 12); tmpstr = tmpstr.Trim(); // get unit name and imei string unitName = tmpstr.Split(new char[] { ',' })[0]; // state can be 'on' or 'off' string state = tmpstr.Split(new char[] { ',' })[1]; // raise the event OnPTTButtonPressChanged?.Invoke(unitName, state.ToLowerInvariant().Equals("on") ? true : false); } // street view was opened or closed else if (response.IndexOf(StreetViewOpened) > -1) { String tmpstr = response.Remove(0, response.IndexOf(StreetViewOpened) + 17); tmpstr = tmpstr.Trim(); bool isOpened = tmpstr.ToLowerInvariant().Equals("true"); // raise the event OnStreetViewStateChanged?.Invoke(isOpened); } else if (response.IndexOf(Landmark) > -1) { ; } else if (response.IndexOf(Done) > -1) { ; } else if (response.IndexOf(Bounds) > -1) { ; } } } private void OnWePageLoadedHandler() { ChangeConsoleState(isConsoleVisible); ChangeMarkersType(iconTheme); CenterMapToPosition(centerPosition); } #endregion private int mapDeadCount = 0; /// /// Display a list of units on the map /// /// List containing the units that needs to be added on the map public void AddVehicles(List vehicles, bool scaleViewBounds) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { lock (InactiveScIdsVehicle) { lock (displayedScIdsVehicle) { foreach (Vehicle veh in vehicles) { // check to see if the unit had reported gps which is not desired to be displayed on the map DateTime positionTime = ((veh).lastValidPositionTime.ConvertGMTToLocal()).GetDTFromSeconds(); // check to see if the unit had reported gps which is not desired to be displayed on the map bool isInactive = !(positionTime.AddMinutes(InactiveTimeoutMinutes) > DateTime.Now) && hideTimeInactiveUnits; // update the reference if active, or inactive and requested emergency if((HideNonEmergUnits && veh.actual_status == Status_for_tab.EMERG) || (!isInactive && !hideNonEmergUnits) || (hideTimeInactiveUnits && showEmergencyForTimeInactiveUnits && veh.actual_status == Status_for_tab.EMERG) ) { if (InactiveScIdsVehicle.ContainsKey(veh.sc_id)) InactiveScIdsVehicle.Remove(veh.sc_id); // remove old instance of vehicle from map if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); // add new instance for vehicle on the map displayedScIdsVehicle.Add(veh.sc_id, veh); } else //if ((!isInactive) || (HideNonEmergUnits && !veh.is_emergency)) { // remove old instance of vehicle from map if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); if (InactiveScIdsVehicle.ContainsKey(veh.sc_id)) InactiveScIdsVehicle.Remove(veh.sc_id); InactiveScIdsVehicle.Add(veh.sc_id, veh); } /* else { // remove old instance of vehicle from map if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); // add new instance for vehicle on the map displayedScIdsVehicle.Add(veh.sc_id, veh); }*/ } } } // store number of units that never reported int totalNumberOfNeverReportedGPS = 0; // store number of units that were removed from dataset due to inactive minutes int totalNumberOfUnitsWithInactiveMinutes = 0; // store number of units that were removed from dataset due to not being in emergency int totalNumberOfUnitsNotInEmergency = 0; IEnumerable> query = vehicles.GroupBy(d => d.group.Id, d => d); // Iterate over each IGrouping in the collection. foreach (IGrouping group in query) { String groupName = group.FirstOrDefault(d => d.IMEI.Length > 0).group.Name; // create unassigned group if the units are part of the groupId 0 groupName = groupName.Trim().Length == 0 ? UNASSIGNED : groupName; List dataSetVehicles = new List(); dataSetVehicles.AddRange(group); //Utils.WriteLine($"Displaying group {groupName}"); int vehiclesWithValidGPSCount = 0; int vehiclesWithInactiveMinutes = 0; int vehiclesNotInEmergency = 0; List list = new List(); list.AddRange(group); while (list.Count > 0) { int toFetchCount = list.Count > datasetChunkSize ? datasetChunkSize : list.Count; // notify number of units displayed until now from total number OnMapProgressChanged?.Invoke(dataSetVehicles.Count - list.Count, dataSetVehicles.Count, "Displaying selected units from " + groupName); // get number of units that had valid gps vehiclesWithValidGPSCount += addToDataSet(groupName, list.GetRange(0, toFetchCount), ref vehiclesWithInactiveMinutes, ref vehiclesNotInEmergency); //Utils.WriteLine($"[vehiclesWithValidGPSCount: {vehiclesWithValidGPSCount}] "); // remove the items passed to the map list.RemoveRange(0, toFetchCount); if (mapDeadCount > 3) ;// list.Clear(); } // force the map to display the group with each update but not pan zoom if (!dispalyedDatasets.Contains(groupName)) putDataSetOnMap(PutOnMapOption.NONE, groupName); // put dataset on map only if valid gps were available if (vehiclesWithValidGPSCount > 0 && !dispalyedDatasets.Contains(groupName)) { // add dataset to the displayed ones dispalyedDatasets.Add(groupName); //putDataSetOnMap(PutOnMapOption.NONE, groupName); } // add the units that weren't displayed due to inactive minutes totalNumberOfUnitsWithInactiveMinutes += vehiclesWithInactiveMinutes; // add the units that weren't displayed due to not being in emergency totalNumberOfUnitsNotInEmergency += vehiclesNotInEmergency; // add number of units that never reported totalNumberOfNeverReportedGPS += (dataSetVehicles.Count - vehiclesWithValidGPSCount - vehiclesWithInactiveMinutes - vehiclesNotInEmergency); OnMapProgressChanged?.Invoke(0, 0, "done"); // first time remove the labels if (scaleViewBounds) SetLabelVisibility(HasLabels, groupName); } // scale the entire if (scaleViewBounds) { // force the map to scale once and fit all units putDataSetOnMap(MapHandler.PutOnMapOption.ONCE, dispalyedDatasets); } } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Update a unit that changed it's gps coordinates /// /// The unit object with the new location public void GPSUpdate(Vehicle vehicle) { // skip units that are not displayed if (!displayedScIdsVehicle.ContainsKey(vehicle.sc_id) && !InactiveScIdsVehicle.ContainsKey(vehicle.sc_id)) { Utils.WriteLine($"GPS updare for units {vehicle.busName} not in the lists"); return; } if (newGPSScIdsVehicle.ContainsKey(vehicle.sc_id)) newGPSScIdsVehicle.Remove(vehicle.sc_id); Utils.WriteLine($"GPS from {vehicle.busName} added to the new GPS queue"); newGPSScIdsVehicle.Add(vehicle.sc_id, vehicle); } /// /// Delete a list of units on the map /// /// List containing the units that needs to be removed from the map public void RemoveVehicles(List vehicles) { removeVehicles(vehicles, true); } /// /// Delete a list of units on the map /// /// List containing the units that needs to be removed from the map /// Specify if the unit was removed by the user or if the unit was removed by the inactive timer private void removeVehicles(List vehicles, bool removedByUser) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { IEnumerable> query = vehicles.GroupBy(d => d.group.Id, d => d); lock (InactiveScIdsVehicle) { lock (displayedScIdsVehicle) { // Iterate over each IGrouping in the collection. foreach (IGrouping group in query) { String groupName = group.FirstOrDefault(d => d.IMEI.Length > 0).group.Name; // create unassigned group if the units are part of the groupId 0 groupName = groupName.Trim().Length == 0 ? UNASSIGNED : groupName; // remove dataset if not vehicle from this group is displayed int countDisplayedVehiclesForThisGroup = displayedScIdsVehicle.Values.ToList().Where(d => d.group.Name.Equals(groupName) && d.lastValidPositionTime > NeverReportedTime).Count(); bool datasetRemoved = false; if ((countDisplayedVehiclesForThisGroup == 0 && !removedByUser) || (countDisplayedVehiclesForThisGroup - group.Count() == 0 && removedByUser)) { RemoveDataset(groupName, false); datasetRemoved = true; } foreach (Vehicle veh in group) { if (removedByUser) { if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); if (InactiveScIdsVehicle.ContainsKey(veh.sc_id)) InactiveScIdsVehicle.Remove(veh.sc_id); } //if (countDisplayedVehiclesForThisGroup - group.Count() != 0) if (!datasetRemoved) removeFromDataSet(groupName, veh.busName, !datasetRemoved, false); } } } } } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Add all units inside this group to the map /// /// Group name that needs to be added /// Vehicles that are inside the group /// Tell if the map needs to scale and adjus viewbounds to the group public void AddGroup(String groupName, List vehicles, bool scaleViewBounds) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { lock (InactiveScIdsVehicle) { lock (displayedScIdsVehicle) { foreach (Vehicle veh in vehicles) { // check to see if the unit had reported gps which is not desired to be displayed on the map DateTime positionTime = ((veh).lastValidPositionTime.ConvertGMTToLocal()).GetDTFromSeconds(); if ((!(positionTime.AddMinutes(InactiveTimeoutMinutes) > DateTime.Now) && HideTimeInactiveUnits) || (((HideNonEmergUnits) && (veh).actual_status != Status_for_tab.EMERG))) { if (InactiveScIdsVehicle.ContainsKey(veh.sc_id)) InactiveScIdsVehicle.Remove(veh.sc_id); InactiveScIdsVehicle.Add(veh.sc_id, veh); } else { // remove old instance of vehicle from map if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); // add new instance for vehicle on the map displayedScIdsVehicle.Add(veh.sc_id, veh); } } } } // create unassigned group if the units are part of the groupId 0 groupName = groupName.Trim().Length == 0 ? UNASSIGNED : groupName; int vehiclesWithValidGPSCount = 0; int vehiclesWithInactiveMinutes = 0; int vehiclesNotInEmergency = 0; List list = new List(); list.AddRange(vehicles); while (list.Count > 0) { int toFetchCount = list.Count > datasetChunkSize ? datasetChunkSize : list.Count; // notify number of units displayed until now from total number OnMapProgressChanged?.Invoke(vehicles.Count - list.Count, vehicles.Count, "Displaying selected units from " + groupName); // get number of units that had valid gps vehiclesWithValidGPSCount += addToDataSet(groupName, list.GetRange(0, toFetchCount), ref vehiclesWithInactiveMinutes, ref vehiclesNotInEmergency); // remove the items passed to the map list.RemoveRange(0, toFetchCount); if (mapDeadCount > 3) ;// list.Clear(); // force the map to display the group with each update but not pan zoom if (!dispalyedDatasets.Contains(groupName)) putDataSetOnMap(PutOnMapOption.NONE, groupName); } if (vehiclesWithValidGPSCount > 0) { // add group to dataset lists and then if (!dispalyedDatasets.Contains(groupName)) { dispalyedDatasets.Add(groupName); // display dataSet //putDataSetOnMap(PutOnMapOption.NONE, groupName); } SetLabelVisibility(hasLabels, groupName); } // trigger event with warning for inactive units if (vehiclesWithInactiveMinutes > 0) OnInactiveUnitsWarning?.Invoke(vehiclesWithInactiveMinutes); if (vehiclesNotInEmergency > 0) OnInactiveUnitsWarning?.Invoke(vehiclesNotInEmergency); // trigger event with nerver reported units if any if (vehicles.Count - vehiclesWithValidGPSCount > 0) OnNeverReportedWarning?.Invoke(vehicles.Count - vehiclesWithValidGPSCount - vehiclesWithInactiveMinutes - vehiclesNotInEmergency); OnMapProgressChanged?.Invoke(0, 0, "done"); } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Delete all units inside this group /// /// Group name that needs to be removed public void RemoveGroup(String groupName) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { lock (InactiveScIdsVehicle) { lock (displayedScIdsVehicle) { // get vehicles from this group that are displayed List vehicles = displayedScIdsVehicle.Values.Where(d => d.group.Name == groupName).ToList(); vehicles.AddRange(InactiveScIdsVehicle.Values.Where(d => d.group.Name == groupName).ToList()); // remove all vehicles from this group that are displayed foreach (Vehicle veh in vehicles) { if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); if (InactiveScIdsVehicle.ContainsKey(veh.sc_id)) InactiveScIdsVehicle.Remove(veh.sc_id); } } } // create unassigned group if the units are part of the groupId 0 groupName = groupName.Trim().Length == 0 ? UNASSIGNED : groupName; RemoveDataset(groupName, true); } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Delete all units inside all the specified groups /// /// List containung all group name that needs to be removed public void RemoveGroups(List groupNames) { // skip empty lists if (groupNames.Count == 0) return; Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { foreach (String groupName in groupNames) { lock (InactiveScIdsVehicle) { lock (displayedScIdsVehicle) { // get vehicles from this group that are displayed List vehicles = displayedScIdsVehicle.Values.Where(d => d.group.Name == groupName).ToList(); vehicles.AddRange(InactiveScIdsVehicle.Values.Where(d => d.group.Name == groupName).ToList()); // remove all vehicles from this group that are displayed foreach (Vehicle veh in vehicles) { if (displayedScIdsVehicle.ContainsKey(veh.sc_id)) displayedScIdsVehicle.Remove(veh.sc_id); if (InactiveScIdsVehicle.ContainsKey(veh.sc_id)) InactiveScIdsVehicle.Remove(veh.sc_id); } } } } RemoveDatasets(groupNames); } Interlocked.Decrement(ref numberOfRunningTasks); }); } public void NearestUnitResponse(int x, int y, double lat, double lng, List vehicles) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { //Send the response to the map int count = 0; StringBuilder sb = new StringBuilder(); for (int i = 0; i < vehicles.Count; i++) { String cmd = getDataSetCmdValuesForVehicle(vehicles[i]); // skip units that are not having any dataset cmd because they didn't reported any Fixed GPS // or ar having gps to older if (cmd.Trim().Length == 0) continue; count++; sb.Append(cmd + ","); } // remove last comma if (sb.ToString().EndsWith(",")) sb.Remove(sb.Length - 1, 1); // execute cmd if at least a unit has Fixed GPS if (sb.ToString().Trim().Length != 0) { mapGoogles?.ExecuteScript($"window.GWTcallback('{Nearest},{(SEQ_ID_REQ ? SeqId + "," : "")}{x},{y},{lat},{lng},{sb.ToString()}');"); //Utils.WriteLine("addToDataSet WAIT"); bool res = semaphore.WaitOne(semaphoreTimeoutMs); //Utils.WriteLine("addToDataSet ENDED " + res); } } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Disable or enable the map console /// /// Boolean telling if the console needs to be displayed or hidden public void ChangeConsoleState(bool isConsoleVisible) { mapGoogles?.ExecuteScript($"window.GWTcallback('{ShowConsole},{(SEQ_ID_REQ ? SeqId + "," : "")}{isConsoleVisible.ToString().ToLower()}');"); } /// /// Disable or enable the labels for the markers /// /// Boolean telling if the markers should have labels displayed or hidden public void SetLabelVisibility(bool hasLabels, String dataSetName) { if (!dispalyedDatasets.Contains(dataSetName)) return; Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { if (hasLabels) mapGoogles?.ExecuteScript($"window.GWTcallback('{PutLabels},{(SEQ_ID_REQ ? SeqId + "," : "")}{dataSetName}');"); else mapGoogles?.ExecuteScript($"window.GWTcallback('{RemoveLabels},{(SEQ_ID_REQ ? SeqId + "," : "")}{dataSetName}');"); } //Utils.WriteLine("SetLabelVisibility WAIT"); bool res = semaphore.WaitOne(semaphoreTimeoutMs); //Utils.WriteLine("SetLabelVisibility ENDED " + res); Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Update the emergency status for an unit /// /// Vehicle that changed it's emergency state public void EmergencyStateUpdate(Vehicle vehicle) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { // skip units that are not displayed if (!displayedScIdsVehicle.ContainsKey(vehicle.sc_id) && !InactiveScIdsVehicle.ContainsKey(vehicle.sc_id)) { //Utils.WriteLine("Skip position for " + vehicle.sc_id); return; } DateTime positionTime = ((vehicle).lastValidPositionTime.ConvertGMTToLocal()).GetDTFromSeconds(); // check to see if the unit had reported gps which is not desired to be displayed on the map bool isInactive = !(positionTime.AddMinutes(InactiveTimeoutMinutes) > DateTime.Now) && hideTimeInactiveUnits; // move the unit from inactive to active if required to show only emergency units if (vehicle.actual_status == Status_for_tab.EMERG && (hideNonEmergUnits || (hideTimeInactiveUnits && showEmergencyForTimeInactiveUnits))) { lock (InactiveScIdsVehicle) { if (InactiveScIdsVehicle.ContainsKey(vehicle.sc_id)) InactiveScIdsVehicle.Remove(vehicle.sc_id); } lock (displayedScIdsVehicle) { if (displayedScIdsVehicle.ContainsKey(vehicle.sc_id)) displayedScIdsVehicle.Remove(vehicle.sc_id); displayedScIdsVehicle.Add(vehicle.sc_id, vehicle); } } // hide non emergency unit if required or emergency one that is inactive by timer else if ((vehicle.actual_status == Status_for_tab.EMERG && hideTimeInactiveUnits && !showEmergencyForTimeInactiveUnits && isInactive) || (vehicle.actual_status != Status_for_tab.EMERG && (hideNonEmergUnits || (hideTimeInactiveUnits && isInactive)))) { lock (InactiveScIdsVehicle) { if (InactiveScIdsVehicle.ContainsKey(vehicle.sc_id)) InactiveScIdsVehicle.Remove(vehicle.sc_id); InactiveScIdsVehicle.Add(vehicle.sc_id, vehicle); } lock (displayedScIdsVehicle) { if (displayedScIdsVehicle.ContainsKey(vehicle.sc_id)) displayedScIdsVehicle.Remove(vehicle.sc_id); } } AddVehicles(new List { vehicle }, false); } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Change the way markers are displayed by the map. This will force the map to change the background color /// of the labels and also their position /// /// Markers type as displayed now public void ChangeMarkersType(IconTheme type) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { mapGoogles.ExecuteScript($"window.GWTcallback('{SetLegacyIcon},{(SEQ_ID_REQ ? SeqId + "," : "")}{(type == IconTheme.CLASSIC ? "true" : "false")}');"); } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Change the center of the map to the desired coordinates and also change the zoom level /// /// Object containing the desired latitude, longitude and zoom level public void CenterMapToPosition(LatLngZoom latLngZoom) { CenterMapToPosition(latLngZoom.Lat, latLngZoom.Lng, latLngZoom.ZoomLevel); } /// /// Change the center of the map to the desired coordinates, keeping the same zoom level /// /// Latitude where the map needs to be centered /// Longitude where the map needs to be centered public void CenterMapToPosition(double lat, double lng) { CenterMapToPosition(lat, lng, -1); } /// /// Change the center of the map to the desired coordinates, and zoom level in case it is different than -1 /// /// Latitude where the map needs to be centered /// Longitude where the map needs to be centered /// Zoom level at which the map needs to be focused. /// In case of -1 value, the zoom level will be left unchanged public void CenterMapToPosition(double lat, double lng, Int32 zoomLevel) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { mapGoogles?.ExecuteScript($"window.GWTcallback('{CenterZoom},{(SEQ_ID_REQ ? SeqId + "," : "")}{lat},{lng},{zoomLevel}');"); } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Set the limit of displayed units on the map /// /// Integer value that represents the maximum number of units that will be displayed public void SetDataSetLimit(int limit) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { mapGoogles?.ExecuteScript($"window.GWTcallback('{SetDatasetLimit},{(SEQ_ID_REQ ? SeqId + "," : "")}{limit}');"); } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Remove an entire dataset from the map /// /// Group dataSet that needs to be removed from the map /// List of maximum 100 units that needs to be added to the map private int addToDataSet(String dataSetName, List vehicles, ref int vehiclesWithInactiveMinutes, ref int vehiclesNotInEmergency) { int count = 0; StringBuilder sb = new StringBuilder(); ReasonSkip reasonSkip = ReasonSkip.NOT_SKIPPED; for (int i = 0; i < vehicles.Count; i++) { reasonSkip = ReasonSkip.NOT_SKIPPED; String cmd = getDataSetCmdValuesForVehicle(vehicles[i], ref reasonSkip, HideTimeInactiveUnits, HideNonEmergUnits); //Utils.WriteLine("addToDataSet " + cmd); // skip units that are not having any dataset cmd because they didn't reported any Fixed GPS // or ar having gps to older if (cmd.Trim().Length == 0) { // count unit to those that were skipped if (reasonSkip == ReasonSkip.INACTIVE_MINUTES) { vehiclesWithInactiveMinutes++; Utils.WriteLine($"addToDataSet {dataSetName} SKIP unit {vehicles[i].busName}", ConsoleColor.DarkYellow); } else if (reasonSkip == ReasonSkip.NO_EMERGENCY) { vehiclesNotInEmergency++; Utils.WriteLine($"addToDataSet {dataSetName} SKIP unit {vehicles[i].busName}", ConsoleColor.DarkYellow); } continue; } else Utils.WriteLine($"addToDataSet {dataSetName} unit {vehicles[i].busName}",ConsoleColor.Yellow); count++; sb.Append(cmd + ","); } // remove last comma if (sb.ToString().EndsWith(",")) sb.Remove(sb.Length - 1, 1); // execute cmd if at least a unit has Fixed GPS if (sb.ToString().Trim().Length != 0) { mapGoogles?.ExecuteScript($"window.GWTcallback('{UpdateDataSet},{(SEQ_ID_REQ ? SeqId + "," : "")}{dataSetName},{sb.ToString()}');"); //Utils.WriteLine("addToDataSet WAIT"); bool res = semaphore.WaitOne(semaphoreTimeoutMs); //Utils.WriteLine("addToDataSet ENDED " + res); } return count; } /// /// Remove an unit from the dataset /// /// Group dataSet from which an unit needs to be removed /// Unit name that needs to be removed from this dataset /// This will force the command to not wait for an answer due to the fact that /// the dataset was already removed and no answer will be received public void removeFromDataSet(String dataSetName, String unitName, bool hasTimeout, bool lockNeeded) { Utils.WriteLine($"removeFromDataSet {dataSetName} unit {unitName}", ConsoleColor.Green); if (lockNeeded) Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { removeFromDataSet(dataSetName, unitName, hasTimeout); } Interlocked.Decrement(ref numberOfRunningTasks); }); else removeFromDataSet(dataSetName, unitName, hasTimeout); } private void removeFromDataSet(String dataSetName, String unitName, bool hasTimeout) { mapGoogles?.ExecuteScript($"window.GWTcallback('{DeleteFromDataset},{(SEQ_ID_REQ ? SeqId + "," : "")}{dataSetName},{unitName}');"); //Utils.WriteLine("removeFromDataSet WAIT"); bool res = semaphore.WaitOne(hasTimeout ? semaphoreTimeoutMs : 100); //Utils.WriteLine("removeFromDataSet ENDED " + res); } /// /// Remove an entire dataset from the map /// /// Group dataSet that needs to be removed from the map public void RemoveDataset(String dataSetName, bool lockNeeded) { if (lockNeeded) Task.Factory.StartNew((Action)delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { RemoveDataset(dataSetName); } Interlocked.Decrement(ref numberOfRunningTasks); }); else RemoveDataset(dataSetName); ; } private void RemoveDataset(String dataSetName) { mapGoogles?.ExecuteScript($"window.GWTcallback('{this.RemoveDataSet},{(SEQ_ID_REQ ? SeqId + "," : "")}{dataSetName}');"); //Utils.WriteLine("RemoveDataset WAIT"); bool res = semaphore.WaitOne(semaphoreTimeoutMs); //Utils.WriteLine("RemoveDataset ENDED " + res); // delete group from dataset lists if (dispalyedDatasets.Contains(dataSetName)) dispalyedDatasets.Remove(dataSetName); } /// /// Remove an entire dataset from the map /// /// Group dataSet that needs to be removed from the map public void RemoveDatasets(List dataSetNames) { Task.Factory.StartNew((Action)delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { StringBuilder sb = new StringBuilder(); foreach (String dataSetName in dataSetNames) { sb.Append(dataSetName + ","); // delete group from dataset lists if (dispalyedDatasets.Contains(dataSetName)) dispalyedDatasets.Remove(dataSetName); } // remove last comma if (sb.ToString().EndsWith(",")) sb.Remove(sb.Length - 1, 1); mapGoogles?.ExecuteScript($"window.GWTcallback('{this.RemoveDataSet},{(SEQ_ID_REQ ? SeqId + "," : "")}{sb.ToString()}');"); foreach (String dataSetName in dataSetNames) { //Utils.WriteLine($"RemoveDataset {dataSetName} WAIT"); bool res = semaphore.WaitOne(semaphoreTimeoutMs); //Utils.WriteLine($"RemoveDataset {dataSetName} ENDED " + res); } } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Display units on map that are in a dataset with option to zoom in order to accomodate all units or not /// /// Option if the command needs to adjust the viewbounds once, always or not /// DataSet name which needs to be added on map and scaled public void putDataSetOnMap(PutOnMapOption option, String dataSetName) { putDataSetOnMap(option, new List() { dataSetName }); } /// /// Display units on map that are in a dataset with option to zoom in order to accomodate all units or not /// /// Option if the command needs to adjust the viewbounds once, always or not /// DataSet names which needs to be added on map and scaled public void putDataSetOnMap(PutOnMapOption option, List dataSetNames) { // do not process if empty dataset names if (dataSetNames.Count == 0) return; // create the list of dataset names divided by comma StringBuilder sb = new StringBuilder(); for (int i = 0; i < dataSetNames.Count; i++) sb.Append(dataSetNames[i] + (i == dataSetNames.Count - 1 ? "" : ",")); mapGoogles?.ExecuteScript($"window.GWTcallback('{PutOnMap},{(SEQ_ID_REQ ? SeqId + "," : "")}{option.ToString().ToLower()},{sb.ToString()}');"); //Utils.WriteLine("putDataSetOnMap WAIT"); bool res = semaphore.WaitOne(semaphoreTimeoutMs); //Utils.WriteLine("putDataSetOnMap ENDED " + res); } /// /// Display an info bubble for the selected unit as it's location /// /// Name of the units for which the info bubble needs to be oppened public void OpenInfoBubble(String unitName) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { mapGoogles?.ExecuteScript($"window.GWTcallback('{OpenInfo},{(SEQ_ID_REQ ? SeqId + "," : "")}{unitName}');"); } Interlocked.Decrement(ref numberOfRunningTasks); }); } private enum ReasonSkip { NO_GPS_FIX, INACTIVE_MINUTES, NOT_SKIPPED, NO_EMERGENCY } /// /// Get the command required by the JS library in order to display a vehicle on the map /// /// Vehicle that needs to be displayed /// skipped /// A string command formated according to the documentation or empty string if the vehicle needs to be skipped /// due to Never reported or due to inactive minutes private String getDataSetCmdValuesForVehicle(Vehicle veh) { ReasonSkip reasonSkip = ReasonSkip.NOT_SKIPPED; return getDataSetCmdValuesForVehicle(veh, ref reasonSkip, false, false); } /// /// Get the command required by the JS library in order to display a vehicle on the map /// /// Vehicle that needs to be displayed /// reference to an object that will specify the reason why this vehicle was /// Set if the units needs to be checked for inactive time /// Set if the units need to be checked for not being in emergency /// skipped /// A string command formated according to the documentation or empty string if the vehicle needs to be skipped /// due to Never reported or due to inactive minutes private String getDataSetCmdValuesForVehicle(Vehicle veh, ref ReasonSkip reasonSkip, bool hideTimeInactiveUnits , bool hideNonEmergUnits) { int addressMaxLength = 55; int mapCallTimeout = 3; int smsLength = 141; Boolean hasVoice = veh.has_voice; Boolean hasText = veh.has_text; Boolean isInCall = veh.inCall; String iconName = veh.IconName; int count = 0; //Utils.WriteLine($"getDataSetCmdValuesForVehicle 000 {count++}"); bool isEmergency = veh.actual_status == Status_for_tab.EMERG; switch(IconType) { case IconType.ARROWS: iconName = $"images/arrow{(isEmergency ? "r" : (veh.vehicleSpeed > 0 ? "g" : "b"))}_{veh.GetHeadingDirection()}.png"; break; case IconType.EMERG: iconName = isEmergency ? "images/peoplepink.png" : iconName; // long version to change also the regular icons //$"images/people{(veh.is_emergency ? "pink" : (veh.vehicleSpeed > 0 ? "green" : "blue"))}.png"; break; case IconType.ICONS: // flag for nearest units to have an opaque icon if not displayed if (!hideTimeInactiveUnits && !((MapElement)veh.MapsHT[MapHashName]).itemCheck) iconName = iconName.Replace("_emerg", "").Replace(".png", "_50o.png"); if (!hideNonEmergUnits && !((MapElement)veh.MapsHT[MapHashName]).itemCheck) iconName = iconName.Replace("_emerg", "").Replace(".png", "_50o.png"); break; } String address = veh.GetLastAddress().Replace(',', ';'); String trimmedAddress = address.Substring(0, address.Length > addressMaxLength ? addressMaxLength : address.Length); if (address.Length != trimmedAddress.Length) address = trimmedAddress + "..."; //Utils.WriteLine($"getDataSetCmdValuesForVehicle 1111 {count++}"); DateTime positionTime = ((veh).lastValidPositionTime.ConvertGMTToLocal()).GetDTFromSeconds(); Utils.WriteLine($"Unit position is : {veh.lastValidPositionTime} | {(veh).lastValidPositionTime.ConvertGMTToLocal()} | {positionTime.ToString()}"); if (positionTime.Year < 2000)// || (Math.Abs(veh.GetLng()) <= 0.1 && Math.Abs(veh.GetLat()) <= 0.1)) { reasonSkip = ReasonSkip.NO_GPS_FIX; //Utils.WriteLine($"getDataSetCmdValuesForVehicle 222 {count++}"); return ""; } // check to see if the unit had reported gps which is not desired to be displayed on the map if (!(positionTime.AddMinutes(InactiveTimeoutMinutes) > DateTime.Now) && hideTimeInactiveUnits) { if (!showEmergencyForTimeInactiveUnits || (showEmergencyForTimeInactiveUnits && veh.actual_status != Status_for_tab.EMERG)) { if (veh.actual_status == Status_for_tab.NOGPSFIX) reasonSkip = ReasonSkip.NO_GPS_FIX; else reasonSkip = ReasonSkip.INACTIVE_MINUTES; return ""; } } if (hideNonEmergUnits && veh.actual_status != Status_for_tab.EMERG) { reasonSkip = ReasonSkip.NO_EMERGENCY; return ""; } //Utils.WriteLine($"getDataSetCmdValuesForVehicle 333 {count++}"); reasonSkip = ReasonSkip.NOT_SKIPPED; return $"{MainForm2.FixComma(veh.GetLat() + "")},{MainForm2.FixComma(veh.GetLng() + "")},{positionTime.ToString("yyyy-MM-dd HH:mm:ss")},{veh.busName}," + $"{veh.GetSpeed()}{(IsMetric ? "kph" : "mph")},{""},{address}," + $"{iconName},{(veh.has_voice ? mapCallTimeout : 0)}, {(veh.has_text ? smsLength : 0)},{veh.inCall}"; } private String landmarkDataSetName = "LandView"; public void ShowLandmarks(List landmarks) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { mapGoogles?.ExecuteScript($"window.GWTcallback('{RemoveDataSet},LandView');"); List list = new List(); list.AddRange(landmarks); while (list.Count > 0) { int toFetchCount = list.Count > datasetChunkSize ? datasetChunkSize : list.Count; // notify number of units displayed until now from total number OnMapProgressChanged?.Invoke(landmarks.Count - list.Count, landmarks.Count, "Displaying landmarks"); StringBuilder sb = new StringBuilder(""); foreach (LandmarkUI land in list.GetRange(0, toFetchCount)) { String cmd = getDataSetCmdValuesForLandmark(land); sb.Append(cmd + ","); } // remove last comma if (sb.ToString().EndsWith(",")) sb.Remove(sb.Length - 1, 1); mapGoogles?.ExecuteScript($"window.GWTcallback('{ShowLandmark},{(SEQ_ID_REQ ? SeqId + "," : "")}{landmarkDataSetName},{sb.ToString()}');"); Utils.WriteLine("ShowLandmarks WAIT"); bool res = semaphore.WaitOne(semaphoreTimeoutMs); Utils.WriteLine("ShowLandmarks ENDED " + res); // remove the items passed to the map list.RemoveRange(0, toFetchCount); } // raise event if (landmarks.Count > 0) { OnMapProgressChanged?.Invoke(0, 0, "done"); OnLandmarksDisplayed?.Invoke(landmarks.Count); } Thread.Sleep(100); putDataSetOnMap(PutOnMapOption.NONE, landmarkDataSetName); SetLabelVisibility(hasLabels, landmarkDataSetName); } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Remove all landmarks from the map /// public void RemoveLandmarks() { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { RemoveDataset(landmarkDataSetName, true); } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Get the command required by the JS library in order to display a landmark on the map /// /// Landmark that needs to be displayed /// A string command formated according to the documentation in order to display the landmark private String getDataSetCmdValuesForLandmark(LandmarkUI landmark) { return $"{MainForm2.FixDoubleLAT(landmark.Latitude + "")},{MainForm2.FixDoubleLNG(landmark.Longitude + "")},{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}," + $"{MainForm2.FixComma(landmark.Name)},{"speed"},{"alt"},{MainForm2.FixComma(landmark.Description).Replace(',', ' ')}," + $"{"landmarks/" + LandZoneCfg.GetLandmarkIcon(landmark.TypeIdx, landmark.Color, landmark.Size)},{"false"}, {"false"},{"false"}"; } public void ShowGeofences(List geofences) { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { mapGoogles?.ExecuteScript($"window.GWTcallback('{DeleteGeofence},{(SEQ_ID_REQ ? SeqId + "," : "")}{"ALL"}');"); foreach (GeofenceUI geofence in geofences) { String cmd = getDataSetCmdValuesForGeofence(geofence); mapGoogles?.ExecuteScript($"window.GWTcallback('{ShowGeofence},{(SEQ_ID_REQ ? SeqId + "," : "")}{cmd}');"); } // raise event for geofences displayed if (geofences.Count > 0) OnGeofencesDisplayed?.Invoke(geofences.Count); } Interlocked.Decrement(ref numberOfRunningTasks); }); } public void RemoveGeofences() { Task.Factory.StartNew(delegate () { Interlocked.Increment(ref numberOfRunningTasks); lock (mapLock) { mapGoogles?.ExecuteScript($"window.GWTcallback('{DeleteGeofence},{(SEQ_ID_REQ ? SeqId + "," : "")}{"ALL"}');"); } Interlocked.Decrement(ref numberOfRunningTasks); }); } /// /// Get the command required by the JS library in order to display a geofence on the map /// /// Geofence that needs to be displayed /// A string command formated according to the documentation in order to display the geofence private String getDataSetCmdValuesForGeofence(GeofenceUI geofence) { String datasetCMD = ""; foreach (GeofencePoint point in geofence.points) { datasetCMD = datasetCMD + MainForm2.FixDoubleLAT(point.Latitude + "") + "," + MainForm2.FixDoubleLNG(point.Longitude + "") + ","; } // get geofence fill color String hexFillColor = "#" + geofence.FillColor.R.ToString("X2") + geofence.FillColor.G.ToString("X2") + geofence.FillColor.B.ToString("X2"); String hexBorderColor = "#" + geofence.BorderColor.R.ToString("X2") + geofence.BorderColor.G.ToString("X2") + geofence.BorderColor.B.ToString("X2"); try { datasetCMD = datasetCMD.Remove(datasetCMD.Length - 1); } catch (Exception ex) { SM.Debug("Ex:" + ex.ToString()); } return geofence.Name + "," + hexFillColor + "," + MainForm2.FixDoubleLAT(geofence.FillOpacity.ToString()) + "," + hexBorderColor + "," + MainForm2.FixDoubleLAT(geofence.BorderOpacity.ToString()) + "," + MainForm2.FixDoubleLAT(geofence.BorderWidth.ToString()) + "," + datasetCMD; } /// /// Class equal with an enum containing a string as a returned value /// This class is used to store the options awailable for the put on map command /// public sealed class PutOnMapOption { private readonly String name; private readonly int value; public static readonly PutOnMapOption NONE = new PutOnMapOption(1, "none"); public static readonly PutOnMapOption ONCE = new PutOnMapOption(2, "once"); public static readonly PutOnMapOption ALWAYS = new PutOnMapOption(3, "always"); private PutOnMapOption(int value, String name) { this.name = name; this.value = value; } public override String ToString() { return name; } } /// /// Returnes true if the unit is displayed on map, otherweise returns false /// /// /// public Boolean IsUnitDisplaydOnMap(string unitName) { if (InactiveScIdsVehicle.ContainsValue((Vehicle)MainForm2.vehicleHT[unitName])) return false; else return true; } #region EVENTS public delegate void MapLoadedEvent(); public event MapLoadedEvent OnMapLoaded; public delegate void NearestRequest(int x, int y, double lat, double lng); public event NearestRequest OnNearestRequest; public delegate void UnitDisplayReqDel(String unitName); public event UnitDisplayReqDel OnUnitDisplayReq; public delegate void LandmarksDisplayed(int number); public event LandmarksDisplayed OnLandmarksDisplayed; public delegate void GeofencesDisplayed(int number); public event GeofencesDisplayed OnGeofencesDisplayed; public delegate void MapProgressChanged(int currentStep, int totalSteps, String message); public event MapProgressChanged OnMapProgressChanged; public delegate void TextMessageRequestEvent(String unitName, String message); public event TextMessageRequestEvent OnTextMessageRequest; public delegate void PTTButtonPressChangedEvent(String unitName, bool isPressed); public event PTTButtonPressChangedEvent OnPTTButtonPressChanged; public delegate void StreetViewStateChangedEvent(bool isOpened); public event StreetViewStateChangedEvent OnStreetViewStateChanged; public delegate void NeverReportedWarningEvent(int count); public event NeverReportedWarningEvent OnNeverReportedWarning; public delegate void InactiveMinuteWarningEvent(int count); public event InactiveMinuteWarningEvent OnInactiveUnitsWarning; public delegate void MapDeadEvent(); public event MapDeadEvent OnMapDead; #endregion } }