The idea came from one of my friends who lives in Malta. The traffic in Malta is very problematic like in many countries. The roads are very narrow and too much vehicles try to go through on the streets and roads. It causes delays in the bus transport and sometimes the passengers must wait for hours. According to the rumors the bus company try to place qr codes on each stations which the passengers can get information about the arrival times of every concerned buses.
I appreciate every constructive criticism.
I appreciate every constructive criticism.
//******************************************************************************************************
//* WatchBus 1.0 - programmed by Munták István *
//* The program simulates bus-traffic and provides informations about the status of the buses. *
//* The user can get arrival times of the buses by the "qrcode" of a station. *
//* Principals: *
//* - However the GPS represents "gps" coords of the buses and stations, *
//* but it's only a pseudo-gps so we CAN'T take it as real gps data! *
//* - For the sake of simplicity the road is ONLY horizontal! *
//* - Every bus goes to the same direction! (However I created a Direction parameter for the backwards *
//* but I don't use it for the time being.) *
//* - Every second means 1 minute *
//* - The bus can change it's speed in a limited interval! *
//* - There is a minimal waiting time on each station but it can be more with a limited time! *
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WatchBus
{
/// <summary>
/// Formal GPS coords NOT real!
/// (double)N, (double)E
/// </summary>
class GPS
{
public double N { get; set; }
public double E { get; set; }
}
/// <summary>
/// Formal Qrcode NOT real!
/// Generated Qcode:ID:{0}|DIRECTION:{1} represents a station
/// </summary>
class QrCode
{
public string Qcode { get; set; }
public int StationId { get; private set; }
public bool Direction { get; private set; }
public QrCode(Station station)
{
Qcode = String.Format("ID:{0}|DIRECTION:{1}", station.Id, station.Direction);
StationId = station.Id;
Direction = station.Direction;
}
}
/// <summary>
/// Ancestor class
/// </summary>
class WatchBusItem
{
public int Id { get; private set; }
public bool Direction { get; private set; }
public GPS Gps { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="id">The Id of the instance</param>
/// <param name="direction">The direction of the instance (left/right side of the road)</param>
/// <param name="gps"></param>
public WatchBusItem(int id, bool direction, GPS gps)
{
if (id < 1) throw new ArgumentOutOfRangeException("The Id must be greater than 0!");
if (gps.N < 0.1 || gps.E < 0.1) throw new ArgumentOutOfRangeException("The GPS coords must be greater than 0.1!");
Id = id;
Direction = direction;
Gps = gps;
}
}
/// <summary>
///
/// </summary>
class Bus:WatchBusItem
{
public TimeTable Time { get; set; }
public int CurrentSpeed { get; set; }
public int AllowedAvarageSpeed { get; set; }
/// <summary>
///
/// </summary>
/// <param name="id">The Id of the instance</param>
/// <param name="direction">The direction of the instance (left/right side of the road)</param>
/// <param name="gps"></param>
/// <param name="allowedavaragespeed">The allowed avarage speed of the bus in km/h (1-120)</param>
public Bus(int id, bool direction, GPS gps, int allowedavaragespeed):base(id,direction,gps)
{
if ((allowedavaragespeed < 1) && (allowedavaragespeed > 120)) throw new ArgumentOutOfRangeException("The allowed avarage speed must be between 1 and 120 km/h!");
AllowedAvarageSpeed = allowedavaragespeed;
}
}
/// <summary>
/// Station class derived from WatchBus ancestor
/// </summary>
class Station:WatchBusItem
{
public QrCode Qrcode { get; private set; }
public int Distance { get; private set; }
public Station(WatchBusItem r):base(r.Id,r.Direction,r.Gps)
{
}
/// <summary>
///
/// </summary>
/// <param name="id">The Id of the instance</param>
/// <param name="direction">The direction of the instance (left/right side of the road)</param>
/// <param name="gps"></param>
public Station(int id, bool direction, GPS gps):this(new WatchBusItem(id,direction,gps))
{
Qrcode = new QrCode(this);
}
}
/// <summary>
/// Road which contains the stations. (Station,Distanace of the previous station)
/// </summary>
class Road
{
public int Id { get; private set; }
private List<Tuple<Station, double>> Stations = new List<Tuple<Station, double>>();
public Road(int id)
{
if (id < 1) throw new ArgumentOutOfRangeException("The Id must be greater than 0!");
Id = id;
}
public void AddStation(Station station)
{
var last=Stations.LastOrDefault();
double distance = 0;
if (last != null)
{
Station s1 = last.Item1;
distance = GetDistance(s1, station);
}
Tuple<Station, double> s2 = new Tuple<Station, double>(station,distance);
if (Stations.Contains(s2))
throw new DuplicateWaitObjectException("This Road already contains this Station!");
Stations.Add(s2);
}
private double GetDistance(Station s1, Station s2)
{
double result=s1.Gps.N - s2.Gps.N;
return result<0?result*(-1):result;
}
/// <summary>
/// Returns with a List<Tuple<Station,double>> where double is the distance from the previous station
/// </summary>
public List<Tuple<Station,double>> GetStation
{
get { return Stations; }
}
}
/// <summary>
/// Timetable of a bus
/// </summary>
class TimeTable
{
public string Table { get; private set; }
public int BusId { get; private set; }
public Dictionary<string, Station> Timetable = new Dictionary<string, Station>();
/// <summary>
///
/// </summary>
/// <param name="table">Name of timetable</param>
/// <param name="busid">Id of bus</param>
public TimeTable(string table, int busid)
{
if (String.IsNullOrEmpty(table)) throw new Exception("The name of timetable can not be empty!");
if (busid < 0) throw new ArgumentOutOfRangeException("The bus ID can not be less than 1!");
Table = table;
BusId = busid;
}
/// <summary>
///
/// </summary>
/// <param name="station"></param>
/// <param name="time">HH:mm</param>
public void AddTimeToTimeTable(Station station, string time)
{
try { Convert.ToDateTime(time); } catch { throw new FormatException("Invalid time format!"); };
Timetable.Add(time, station);
}
/// <summary>
/// Returns with a Dictionary where Key:Time Value:Station
/// </summary>
public Dictionary<string,Station> GetTimeTable
{
get { return Timetable; }
}
}
/// <summary>
/// Thread safe random class
/// </summary>
class random
{
public int result { get; private set; }
public random(Random rng, int max)
{
lock (rng)
{
result = rng.Next(max);
}
}
}
/// <summary>
/// Bus-handler class. Provides the movement of a bus.
/// </summary>
class BusMovement
{
//Maximum decelaration
private int MaxDeceleration = 5;
//Maximum plus waiting time at a station
private int MaxExtraWaitingTime = 5;
//Minimum waiting time at station
private int MinWaitingTime = 5;
public Road Road { get;private set; }
private Bus bus { get; set; }
//Start GPS coords of the bus
private GPS BusStartGps { get; set; }
//Elapsed time between two stations
public double ElapsedTime { get; set; }
public int stationId { get; set; }
//Current time
public string TimeOfDay { get; set; }
//Information about the status of a bus
public string Info { get; private set; }
public TimeTable table { get; set; }
System.Timers.Timer timer = new System.Timers.Timer(1000);
/// <summary>
/// Starts the bus on it's road
/// </summary>
/// <param name="b">Bus</param>
/// <param name="road">Road</param>
public void BusStart(Bus b, Road road)
{
timer.Elapsed += new System.Timers.ElapsedEventHandler((sender,e)=>BusStatus());
BusStartGps = new GPS(); double x = b.Gps.N; double y = b.Gps.E;
BusStartGps.N = x; BusStartGps.E = y;
Road = road;
bus = b;
timer.Start();
}
/// <summary>
/// Delegated method for timer which changes the status of the bus in every second.
/// </summary>
private void BusStatus()
{
int stationIdCurrent=IsBusAtStation();
if (stationIdCurrent!= 0) //Arrive to the station
{
//Set a random waiting time
int WaitingTime = SetWaitingTime();
//Stop the timer because we start the waiting in a task
timer.Stop();
//The task is delaying for WaitingTime (it means minutes)
Task t = Task.Run(async() =>
{
ElapsedTime = 0;
stationId = stationIdCurrent + 1;
//Info = String.Format("The {0}.bus is waiting at station {1}",bus.Id, stationIdCurrent);
TimeOfDay = Convert.ToDateTime(TimeOfDay).AddMinutes(WaitingTime).TimeOfDay.ToString();
await Task.Delay(WaitingTime * 1000);
});
t.Wait(); //Don't let to continue the thread while waiting time not elapsed
timer.Start(); //Start the timer again
}
//Set a random current speed
bus.CurrentSpeed = SetCurrentSpeed();
ElapsedTime += (float)1 / (float)60; //+1 minute in hour
TimeOfDay = Convert.ToDateTime(TimeOfDay).AddMinutes(1).TimeOfDay.ToString();
//If the actual station doesn't exist in the Road then it is the final station
if (Road.GetStation.Exists(x => x.Item1.Id == stationId))
{
double s = GetBusDistanceToNextStation(stationId, ElapsedTime);
//The bus has reached the station
if (s <= 0)
{
bus.Gps.N = Road.GetStation.Where(x => x.Item1.Id == stationId).First().Item1.Gps.N;
//Info = String.Format("The {0}.bus arrived at station {1}", bus.Id, stationId);
}
else
{
double t = GetBusArrivalTime(s); //Arrival time to the next station in hour
string ArrivalTime = Convert.ToDateTime(TimeOfDay).AddHours(Convert.ToDouble(t)).ToString("HH:mm");
string slip = GetSlipTime(stationId, ArrivalTime); //Difference of the required time and real arrival time
//Info = String.Format("The {0}.bus will arrive to station {1} in {2:F} hours, D:{3:F}, arrival time is {4}. Slip:{5} mins", bus.Id, stationId, t, s, ArrivalTime,slip);
}
}
else
{
timer.Stop();
Console.WriteLine("The {0}.bus arrived it's final station", bus.Id);
}
}
/// <summary>
/// Returns with remained distance to the next station
/// </summary>
/// <param name="id">Station Id</param>
/// <param name="et">Elapsed time</param>
/// <returns></returns>
public double GetBusDistanceToNextStation(int id, double et)
{
double d = bus.CurrentSpeed * et; //s=v*t travelled distance
double distance = d;
distance = Road.GetStation.Where(x => x.Item1.Id == id).First().Item2;
bus.Gps.N =BusStartGps.N+d;
return distance - d;
}
private int IsBusAtStation()
{
if (Road.GetStation.Exists(x => x.Item1.Gps.N == bus.Gps.N && x.Item1.Gps.E == bus.Gps.E))
{
return Road.GetStation.Where(x => x.Item1.Gps.N == bus.Gps.N && x.Item1.Gps.E == bus.Gps.E).First().Item1.Id;
}
return 0;
}
/// <summary>
/// Returns with a time which needed to arrive to the next station
/// </summary>
/// <param name="s">Remained distance</param>
/// <returns></returns>
public double GetBusArrivalTime(double s)
{ //t=s/v but if the bus is waiting at a station the v=0
//In this case we have to calculate with the allowed avarage speed!
double t = s / (bus.CurrentSpeed==0 ? bus.AllowedAvarageSpeed:bus.CurrentSpeed);
return t;
}
private int SetCurrentSpeed()
{
int AvarageSpeed = bus.AllowedAvarageSpeed;
Random rspeed = new Random();
random r = new random(rspeed, MaxDeceleration);
AvarageSpeed -= r.result;
return AvarageSpeed;
}
private int SetWaitingTime()
{
Random rwait = new Random();
random r = new random(rwait, MaxExtraWaitingTime);
int waitingtime = r.result;
return waitingtime;
}
private string GetSlipTime(int stationid, string arrivaltime)
{
DateTime t1 = Convert.ToDateTime(arrivaltime);
Station s = Road.GetStation.Where(x => x.Item1.Id == stationid).First().Item1;
DateTime t2 = Convert.ToDateTime(table.GetTimeTable.Where(x => x.Value == s).First().Key);
double slip = (t1 - t2).TotalMinutes;
if (slip > 0) return slip.ToString();
return "0";
}
/*public string GetInfo()
{
return Info;
}*/
/// <summary>
/// Returns with information of arrival and slip time
/// </summary>
/// <param name="qrcode">Qrcode</param>
/// <returns></returns>
public string GetInfo(QrCode qrcode)
{
if (Road.GetStation.Exists(x => x.Item1.Qrcode.Qcode == qrcode.Qcode))
{ //Distance to the very next station
double s = GetBusDistanceToNextStation(stationId, ElapsedTime);
//Remained time to the very next station
double t = GetBusArrivalTime(s);
//Arrival time to the very next station
string ArrivalTime = Convert.ToDateTime(TimeOfDay).AddHours(Convert.ToDouble(t)).ToString("HH:mm");
//Remained station to the queried station without very next station
var stations = Road.GetStation.Where(x => x.Item1.Id > stationId && x.Item1.Id <= qrcode.StationId);
if ((stations.Count() == 0) && (stationId!=qrcode.StationId)) return String.Format("The {0}.bus has already left station {1}", bus.Id, qrcode.StationId);
t = 0;
foreach (Tuple<Station, double> st in stations)
{
s = st.Item2;
t+=GetBusArrivalTime(s)+(MinWaitingTime*(float)1/(float)60); //Adding minimum waiting time to every stations
}
ArrivalTime = Convert.ToDateTime(ArrivalTime).AddHours(Convert.ToDouble(t)).ToString("HH:mm");
string slip = GetSlipTime(qrcode.StationId, ArrivalTime);
//T:Current time
Info = String.Format("The {0}.bus will arrive to station {1}, arrival time is {2}. Slip:{3} mins. T:{4:HH:mm}", bus.Id, qrcode.StationId, ArrivalTime, slip,TimeOfDay);
return Info;
}
return "N/A";
}
}
class Program
{
/// <summary>
/// Returns with a list of bus status
/// </summary>
/// <param name="qrcode">Qrcode of queried station</param>
/// <param name="bms">Collection of BusMovement classes</param>
/// <returns></returns>
static List<string> GetAllBusStatus(string qrcode, ICollection<BusMovement> bms)
{
List<string> result=new List<string>();
foreach (BusMovement bm in bms)
{
if (bm.Road.GetStation.Exists(x => x.Item1.Qrcode.Qcode == qrcode))
{
QrCode code =bm.Road.GetStation.Where(x => x.Item1.Qrcode.Qcode == qrcode).First().Item1.Qrcode;
string r = bm.GetInfo(code);
if (r != "N/A") result.Add(r);
}
}
return result;
}
static void Main(string[] args)
{
GPS gpsbus = new GPS(); gpsbus.N = 10; gpsbus.E = 10;
GPS gps1=new GPS(); gps1.N=10; gps1.E=10;
GPS gps2=new GPS(); gps2.N=40; gps2.E=10;
GPS gps3=new GPS(); gps3.N=70; gps3.E=10;
GPS gps4=new GPS(); gps4.N=100; gps4.E=10;
GPS gps5=new GPS(); gps5.N=130; gps5.E=10;
Road r1 = new Road(1);
r1.AddStation(new Station(1, true, gps1));
r1.AddStation(new Station(2, true, gps2));
r1.AddStation(new Station(3, true, gps3));
r1.AddStation(new Station(4, true, gps4));
r1.AddStation(new Station(5, true, gps5));
TimeTable t1 = new TimeTable("First",1);
t1.AddTimeToTimeTable(r1.GetStation.Where(x => x.Item1.Id == 1).First().Item1, "15:00");
t1.AddTimeToTimeTable(r1.GetStation.Where(x => x.Item1.Id == 2).First().Item1, "16:05");
t1.AddTimeToTimeTable(r1.GetStation.Where(x => x.Item1.Id == 3).First().Item1, "17:10");
t1.AddTimeToTimeTable(r1.GetStation.Where(x => x.Item1.Id == 4).First().Item1, "18:15");
t1.AddTimeToTimeTable(r1.GetStation.Where(x => x.Item1.Id == 5).First().Item1, "19:20");
//BUS1
Bus b1 = new Bus(1, true, gpsbus, 30);
BusMovement bm = new BusMovement();
bm.ElapsedTime = 0;
bm.stationId = 0;
bm.TimeOfDay = "15:00";
bm.table = t1;
//BUS2
Bus b2 = new Bus(2, true, gpsbus,30);
BusMovement bm2 = new BusMovement();
bm2.ElapsedTime = 0;
bm2.stationId = 0;
bm2.TimeOfDay = "15:00";
bm2.table = t1;
List<BusMovement> bms=new List<BusMovement>();
bms.Add(bm); bms.Add(bm2);
Console.WriteLine("Please press any key to start the buses...");
Console.ReadKey();
//Buses start in two synchronized tasks
Task[] buses = new Task[2];
buses[0] = Task.Run(() => bm.BusStart(b1, r1));
buses[1] = Task.Run(() => bm2.BusStart(b2, r1));
Task.WaitAll(buses);
bool stop = false;
Task t = Task.Run(() =>
{
while (!stop)
{
switch (Console.ReadKey().Key)
{
//case ConsoleKey.LeftArrow: Console.WriteLine(bm.GetInfo()); break;
//case ConsoleKey.RightArrow: Console.WriteLine(bm2.GetInfo()); break;
//UpArrow keypress represents a query with QrCode
case ConsoleKey.UpArrow:
{
List<string> bmresult=new List<string>();
string qcode = "ID:3|DIRECTION:True";
bmresult=GetAllBusStatus(qcode,bms);
if (bmresult.Count>0)
foreach (string result in bmresult)
{
Console.WriteLine(result);
}
break;
}
case ConsoleKey.DownArrow:{ Console.WriteLine(); break;}
default: stop = true; break;
}
}
});
t.Wait();
Console.ReadLine();
}
}
}