using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using MySql.Data.MySqlClient;
using MySql.Data;
using System.Configuration;
using MyLog;
using MapCore;
using MapTrac.CommonMap;
using MapTrac.Common;
using System.Xml.Linq;
namespace TermLocationService
{
enum LOCATION_STATUS
{
NEW_DATA = 0,
LOCATION_OK = 1,
LOCATION_NOT_IN_ZONE = 2,
ZONE_CONTAIN_NO_BLOCKS = 3
}
public partial class TermLocationService : ServiceBase
{
private int threadCount = Int32.Parse(ConfigurationManager.AppSettings["ThreadCount"].ToString());
private int sleepInterval = Int32.Parse(ConfigurationManager.AppSettings["TimerInterval"].ToString());
private string ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ToString();
private List<CZoneGroup> m_lsZoneGroup = new List<CZoneGroup>();
private Dictionary<ulong, DataTable> m_dStackTable = new Dictionary<ulong, DataTable>();
bool threadControl;
List<CLocation>[] sublist;
ManualResetEvent childMRE = new ManualResetEvent(false);
ManualResetEvent mainMRE = new ManualResetEvent(false);
MySqlConnection DBConnection = null;
private MapEngine MapEngine { get; set; }
public TermLocationService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
Log.WriteLine(DateTime.Now.ToString() + ": The service was started.");
threadControl = true;
Thread mainThread = new Thread(new ThreadStart(StartProcess));
mainThread.Name = "Terminal Location Retriever";
mainThread.Start();
}
protected override void OnStop()
{
threadControl = false;
childMRE.Set();
mainMRE.Set();
if (DBConnection != null)
{
DBConnection.Close();
}
Log.WriteLine(DateTime.Now.ToString() + ": The service is stopped.");
}
public static void Main(string[] args)
{
Log.IsConsole = true;
new TermLocationService().OnStart(args);
Thread.Sleep(Timeout.Infinite);
}
private void GetTermDetailZones(ulong zoneId)
{
MySqlConnection DBConnection = new MySqlConnection(ConnectionString);
DBConnection.Open();
try
{
// Get Zone Points of each District
using (MySqlCommand DBCommand = DBConnection.CreateCommand())
{
DBCommand.CommandText = "SP_Client_Get_CompanyZonePointDetail";
DBCommand.CommandType = CommandType.StoredProcedure;
DBCommand.Parameters.AddWithValue("szCoID", "Term");
DBCommand.Parameters.AddWithValue("nZoID", zoneId);
using (MySqlDataReader DBReader = DBCommand.ExecuteReader())
{
List<CZonePoint> zoneData = new List<CZonePoint>();
while (DBReader.Read())
{
int nCol = 0;
zoneData.Add(new CZonePoint()
{
ZoneID = DBReader.GetUInt64(nCol++),
CompanyID = DBReader.GetString(nCol++),
ZoneName = DBReader.GetString(nCol++),
ZoneDescription = DBReader.GetString(nCol++),
ZoneType = (EZoneType)DBReader.GetByte(nCol++),
ZonePointID = DBReader.GetUInt64(nCol++),
Latitude = DBReader.GetDouble(nCol++),
Longitude = DBReader.GetDouble(nCol++),
Radius = DBReader.GetDouble(nCol++),
});
}
m_lsZoneGroup.AddRange(zoneData.ToZoneGroupList());
}
}
}
catch (Exception e)
{
Log.WriteLine(DateTime.Now.ToString() + ": Error: " + e.ToString());
}
finally
{
if (DBConnection != null && DBConnection.State == ConnectionState.Open)
{
DBConnection.Close();
}
}
}
private void Refresh_GetTermZones()
{
MySqlConnection DBConnection = new MySqlConnection(ConnectionString);
DBConnection.Open();
try
{
// Get District Zone Ids
using (MySqlCommand DBCommand = DBConnection.CreateCommand())
{
DBCommand.CommandText = "SELECT DistrictZoID FROM DISTRICTCFG;";
DBCommand.CommandType = CommandType.Text;
using (MySqlDataReader DBReader = DBCommand.ExecuteReader())
{
// clear zone cache
m_lsZoneGroup.Clear();
while (DBReader.Read())
{
int nCol = 0;
ulong zoneId = DBReader.GetUInt64(nCol++);
// Zone detail points of all sides
GetTermDetailZones(zoneId);
// update cache blocks/stacks by zoneid
m_dStackTable[zoneId] = GetStacksByZoneId(zoneId);
}
}
}
}
catch (Exception e)
{
Log.WriteLine(DateTime.Now.ToString() + ": Error: " + e.ToString());
}
finally
{
if (DBConnection != null && DBConnection.State == ConnectionState.Open)
{
DBConnection.Close();
}
}
}
private DataTable GetStacksByZoneId(ulong zoneid)
{
DataTable dt = new DataTable();
MySqlConnection DBConnection = new MySqlConnection(ConnectionString);
DBConnection.Open();
try
{
// Get District Zone Ids
using (MySqlCommand DBCommand = DBConnection.CreateCommand())
{
DBCommand.CommandText = "SP_Terminal_Get_StacksByZoneId";
DBCommand.CommandType = CommandType.StoredProcedure;
DBCommand.Parameters.AddWithValue("nZoneId", zoneid);
using (MySqlDataAdapter dbAdp = new MySqlDataAdapter(DBCommand))
{
dbAdp.Fill(dt);
return dt;
}
}
}
catch (Exception e)
{
Log.WriteLine(DateTime.Now.ToString() + ": Error: " + e.ToString());
}
finally
{
if (DBConnection != null && DBConnection.State == ConnectionState.Open)
{
DBConnection.Close();
}
}
return null;
}
private void StartProcess()
{
string threadName = Thread.CurrentThread.Name;
// TODO: Need a thread to update ZoneGroups every minute or so....
// TODO: Careful since Stacks read/write in a thread, need to lock table.
Refresh_GetTermZones();
// Update the Term Address in TERMINAL_REPORT table
for (int i = 1; i <= threadCount; i++)
{
Thread thread = new Thread(new ThreadStart(() => UpdateStreet(new MySqlConnection(ConnectionString))));
thread.Name = "Terminal Location Updater " + i;
thread.Start();
}
sublist = new List<CLocation>[threadCount];
MySqlConnection DBConnection = new MySqlConnection(ConnectionString);
while (threadControl)
{
int rowPer = 0;
int listCount = 0;
for (int i = 0; i < threadCount; i++)
{
sublist[i] = new List<CLocation>();
}
try
{
Log.WriteLine(DateTime.Now.ToString() + ": Retrieving LAT/LON...");
List<CLocation> lsLocation = GetLocation(DBConnection);
listCount = lsLocation.Count;
if (lsLocation != null && listCount >= threadCount)
{
rowPer = listCount / threadCount;
}
//Divide main list to sublist for child threads.
int arrayPos = 0;
int fixRowPer = rowPer;
for (int i = 0; i < listCount; i++)
{
sublist[arrayPos].Add(lsLocation[i]);
if (arrayPos != threadCount - 1)
{
if (i == rowPer)
{
rowPer += fixRowPer;
arrayPos++;
}
}
}
childMRE.Set();
mainMRE.WaitOne();
childMRE.Reset();
mainMRE.Reset();
}
catch(Exception e)
{
Log.WriteLine(DateTime.Now.ToString() + ": Error: " + e.ToString());
}
Thread.Sleep(1000 * sleepInterval);
}
Log.WriteLine(DateTime.Now.ToString() + ": " + threadName + " stopped.");
}
private List<CLocation> GetLocation(MySqlConnection DBConnection)
{
List<CLocation> lsLocation = null;
try
{
lsLocation = new List<CLocation>();
DBConnection.Open();
MySqlCommand DBCommand = DBConnection.CreateCommand();
DBCommand.CommandText = "CALL SP_Terminal_Location_Search";
MySqlDataReader DBReader = DBCommand.ExecuteReader();
while (DBReader.Read())
{
int LocID = Int32.Parse(DBReader[0].ToString());
double lat = Double.Parse(DBReader[1].ToString());
double lon = Double.Parse(DBReader[2].ToString());
lsLocation.Add(new CLocation()
{
LocID = LocID,
LocLat = lat,
LocLon = lon
});
}
DBReader.Close();
DBReader.Dispose();
DBConnection.Close();
}
catch (Exception e)
{
Log.WriteLine(DateTime.Now.ToString() + ": Error: " + e.ToString());
}
finally
{
if (DBConnection != null && DBConnection.State == ConnectionState.Open)
{
DBConnection.Close();
}
}
return lsLocation;
}
private void UpdateStreet(MySqlConnection DBConnection)
{
string threadName = Thread.CurrentThread.Name;
Log.WriteLine(DateTime.Now.ToString() + ": " + threadName + " started." );
while (threadControl)
{
try
{
childMRE.WaitOne();
List<CLocation> lsLocation = GetSublist(threadName);
if (lsLocation != null && lsLocation.Count != 0)
{
Log.WriteLine(DateTime.Now.ToString() + ": " + threadName + ": Updating location.");
var watch = System.Diagnostics.Stopwatch.StartNew();
//Process data first before opening update connection.
for (int i = 0; i < lsLocation.Count; i++)
{
ulong zoneId = 0;
// check if LAT/LON belongs to a Zone
foreach (CZoneGroup zoneGP in m_lsZoneGroup)
{
if (zoneGP.IsInZone(lsLocation[i].LocLat, lsLocation[i].LocLon))
{
zoneId = zoneGP.ZoneID;
break;
}
}
// Our LAT/LON is within a District ZoneId
if (zoneId > 0)
{
DataTable dt = null;
m_dStackTable.TryGetValue(zoneId, out dt);
if (dt != null && dt.Rows.Count > 0)
{
double distance = 5000.0; // start with some large number
// Compare LAT/LON to all Stacks in Zone
DataRow dr = GetClosestStackId(dt, lsLocation[i].LocLat, lsLocation[i].LocLon, ref distance);
if (dr != null)
{
lsLocation[i].Distance = distance * 1000.0; // covert to meters
lsLocation[i].Terminal = dr["TerminalID"].ToString();
lsLocation[i].District = dr["DistrictID"].ToString();
lsLocation[i].Block = dr["BlockID"].ToString();
lsLocation[i].Stack = dr["StackNoName"].ToString();
lsLocation[i].Status = (short)LOCATION_STATUS.LOCATION_OK;
}
}
else
{
lsLocation[i].Status = (short)LOCATION_STATUS.ZONE_CONTAIN_NO_BLOCKS;
}
}
else
{
lsLocation[i].Status = (short) LOCATION_STATUS.LOCATION_NOT_IN_ZONE;
}
}
DBConnection.Open();
MySqlCommand CommandUpdate;
foreach (CLocation oLocation in lsLocation)
{
CommandUpdate = DBConnection.CreateCommand();
CommandUpdate.CommandText = "SP_Terminal_Location_Update";
CommandUpdate.CommandType = CommandType.StoredProcedure;
CommandUpdate.Parameters.AddWithValue("vLocID", oLocation.LocID);
CommandUpdate.Parameters.AddWithValue("vDistance", oLocation.Distance);
CommandUpdate.Parameters.AddWithValue("vTerminal", oLocation.Terminal);
CommandUpdate.Parameters.AddWithValue("vDistrict", oLocation.District);
CommandUpdate.Parameters.AddWithValue("vBlock", oLocation.Block);
CommandUpdate.Parameters.AddWithValue("vStack", oLocation.Stack);
CommandUpdate.Parameters.AddWithValue("vStatus", oLocation.Status);
CommandUpdate.ExecuteNonQuery();
}
DBConnection.Close();
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
Log.WriteLine(DateTime.Now.ToString() + ": " + threadName + ": Location updated. " + elapsedMs + "ms");
}
//Make sure all threads have retrieved the sublists for main thread to repopulate.
int emptyList = 0;
for (int i = 0; i < threadCount; i++)
{
if (sublist[i] == null)
{
emptyList++;
}
}
if (emptyList == threadCount)
{
if (!mainMRE.WaitOne(0))
{
mainMRE.Set();
}
}
}
catch (Exception e)
{
Log.WriteLine(DateTime.Now.ToString() + ": Error: " + e.InnerException);
}
finally
{
if (DBConnection != null && DBConnection.State == ConnectionState.Open)
{
DBConnection.Close();
}
}
}
Log.WriteLine(DateTime.Now.ToString() + ": " + threadName + " stopped.");
}
private List<CLocation> GetSublist(string threadName)
{
List<CLocation> returnList = null;
try
{
int arrPos = Int32.Parse(threadName[threadName.Length - 1].ToString());
returnList = sublist[arrPos - 1];
sublist[arrPos - 1] = null;
}
catch { }
return returnList;
}
private DataRow GetClosestStackId(DataTable dt, double dlat, double dlon, ref double dtClosest)
{
DataRow drClosest = null;
try
{
foreach (DataRow dr in dt.Rows)
{
double lat = dr["LAT"].ToDouble(0.0);
double lon = dr["LON"].ToDouble(0.0);
// TODO: Should consider the block direction, use some advanced math
// formula to readjust LAT/LON and recalculate distance
// calculate distance, get the closest/nearest to Stack
double distance = CMapEngine.SharedEngine.LatLonDif2DistKM(dlat, dlon, lat, lon);
if (distance < dtClosest)
{
dtClosest = distance;
drClosest = dr;
}
}
}
catch { }
return drClosest;
}
}
public class CLocation
{
public CLocation()
{
LocID = 0;
LocLat = 0.0;
LocLon = 0.0;
Distance = 0.0;
Terminal = string.Empty;
District = string.Empty;
Block = string.Empty;
Stack = string.Empty;
Status = (short)LOCATION_STATUS.NEW_DATA;
}
public int LocID { get; set; }
public double LocLat { get; set; }
public double LocLon { get; set; }
public double Distance { get; set; }
public string Terminal { get; set; }
public string District { get; set; }
public string Block { get; set; }
public string Stack { get; set; }
public short Status { get; set; }
}
}