using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace PeerJ { class AutonomousVehicleArticleDBSCAN { double epsilon = 100; int minAccidentNum = 3; int county = 2; double probabilityLimit = 0.2; ReasonDetermination rd = new ReasonDetermination(); List population = new List(); Accidents[] allAccidents = new Accidents[] { new Accidents(), new Accidents() }; List[] blackSpotCandidates = new List[] { new List(), new List() }; List[] blackSpots = new List[] { new List(), new List() }; public void RunMultipleTests() { StreamWriter swc = new StreamWriter(@"x:\Research\RoadSafety\data\test2\comparision.txt"); swc.WriteLine("county\tepsilon\tminAccidentNum\tprobabilityLimit\tAccNumA\tAccNumB\tTR\tFA\tFB\tScore\tT1\tT1SA\tT2\tT3"); swc.Close(); int[] testCounties = new int[] { 8, 10 }; foreach (int icounty in testCounties) { county = icounty; epsilon = 100; { minAccidentNum = 5; { Console.Write("Candidates [" + county + "\t" + epsilon + "\t" + minAccidentNum + "]:"); //FindBlackSpotCandidatesSlidingWindow(); FindBlackSpotCandidatesDBSCAN(); Console.WriteLine(blackSpotCandidates[0].Count + "\t" + blackSpotCandidates[1].Count); double[] probabilityLimits = new double[] { 0.05 }; foreach (double iprobabilityLimit in probabilityLimits) { probabilityLimit = iprobabilityLimit; Console.Write(" Black spots [" + county + "\t" + epsilon + "\t" + minAccidentNum + "\t" + probabilityLimit + "]:"); TestBlackSpots(); Console.Write(blackSpots[0].Count + "\t" + blackSpots[1].Count); ClusterSetCompare csc = new ClusterSetCompare(allAccidents[0], allAccidents[1], blackSpots[0], blackSpots[1], 300, ScoreOfAccident); File.WriteAllText(@"x:\Research\RoadSafety\data\test2\compare-" + Prefix + ".txt", csc.Log); swc = new StreamWriter(@"x:\Research\RoadSafety\data\test2\comparision.txt", true); swc.WriteLine(county + "\t" + epsilon + "\t" + minAccidentNum + "\t" + probabilityLimit + "\t" + csc.AccNumA + "\t" + csc.AccNumB + "\t" + csc.TR + "\t" + csc.FA + "\t" + csc.FB + "\t" + csc.Score + "\t" + csc.T1 + "\t" + csc.T1SA + "\t" + csc.T2 + "\t" + csc.T3); swc.Close(); Console.WriteLine("\t" + csc.Score); } } } } } private void FindBlackSpotCandidatesDBSCAN() { sess.DBSCANBlackSpot.MinAccidentDens = 0.0001; sess.DBSCANBlackSpot.MinAccidentNum = minAccidentNum; sess.DBSCANBlackSpot.Epsilon = epsilon; sess.DBSCANBlackSpot.MinArea = 0; sess.DBSCANBlackSpot.Parameters.WeightDeadAccident = 1; sess.DBSCANBlackSpot.Parameters.WeightHardAccident = 1; sess.DBSCANBlackSpot.Parameters.WeightLightAccident = 1; sess.DBSCANBlackSpot.Parameters.WeightPropAccident = 1; for (int period = 0; period <= 1; period++) { LoadAccidentsForCounty(sess.DBSCANBlackSpot.Accidents, county, 2011 + period * 4, 2014 + period * 4, false, period == 0); Console.WriteLine("Full accident count[" + county + "," + period + "]=" + sess.DBSCANBlackSpot.Accidents.Count); allAccidents[period].Clear(); foreach (Accident acc in sess.DBSCANBlackSpot.Accidents) allAccidents[period].Add(acc); sess.DBSCANBlackSpot.ProcessBlackSpotSearch(true); blackSpotCandidates[period] = sess.DBSCANBlackSpot.SearchResults; } } private void FindBlackSpotCandidatesSlidingWindow() { sess.BlackSpot.MinAccidentDens = 0.01; sess.BlackSpot.MinAccidentNum = minAccidentNum; sess.BlackSpot.MinSectionLength = 250; sess.BlackSpot.Parameters.WeightDeadAccident = 1; sess.BlackSpot.Parameters.WeightHardAccident = 1; sess.BlackSpot.Parameters.WeightLightAccident = 1; sess.BlackSpot.Parameters.WeightPropAccident = 1; for (int period = 0; period <= 1; period++) { LoadAccidentsForCounty(sess.BlackSpot.Accidents, county, 2011 + period * 4, 2014 + period * 4, true); Console.WriteLine("Full accident count[" + county + "," + period + "]=" + sess.BlackSpot.Accidents.Count); allAccidents[period].Clear(); foreach (Accident acc in sess.BlackSpot.Accidents) allAccidents[period].Add(acc); sess.BlackSpot.ProcessBlackSpotSearch(true); blackSpotCandidates[period] = sess.BlackSpot.SearchResults; } } private void TestBlackSpots() { for (int period = 0; period <= 1; period++) { blackSpots[period].Clear(); StreamWriter sw = new StreamWriter(@"x:\Research\RoadSafety\data\test2\bs-" + Prefix + "-" + period + ".txt"); int bi = 0; foreach (BlackSpot bs in blackSpotCandidates[period]) { double probability = WelchTest(bs); if (probability < probabilityLimit) { blackSpots[period].Add(bs); sw.WriteLine("Blackspot " + (bi++) + " " + bs.DbScanArea + " " + probability); PropertyDescriptor pdNature = OrbitApplication.Instance.GetDescriptor(PFC.FixedAccidentNature); PropertyDescriptor pdWeather = OrbitApplication.Instance.GetDescriptor(PFC.FixedWeather); PropertyDescriptor pdRoadQuality = OrbitApplication.Instance.GetDescriptor(PFC.FixedRoadQuality); foreach (Accident acc in bs.GetAccidents()) { float slipperyScore = rd.Evaluate(ReasonDetermination.ReasonType.Slippery, acc); sw.WriteLine(" " + acc.Ident + "\t" + acc.RoadNumber + "\t" + acc.Position + "\t" + acc.Latitude + "\t" + acc.Longitude + "\t" + slipperyScore + "\t" + acc.RoadNumber + "\t" + acc.Time + "\t" + acc.Result + "\t" + pdRoadQuality.CodeTable[acc[pdRoadQuality]] + "\t" + pdWeather.CodeTable[acc[pdWeather]] + "\t" + pdNature.CodeTable[acc[pdNature]] ); } } } sw.Close(); } } private void LoadAllAccidents() { sess.DBSCANBlackSpot.Accidents.Filters.Clear(); sess.DBSCANBlackSpot.Accidents.Load(); Console.WriteLine("All accident count:" + sess.DBSCANBlackSpot.Accidents.Count); } private void LoadAccidentsForCounty(FilteredAccidents filteredAccidents, int county, int? yearFrom, int? yearTo, bool skip9600 = false, bool saveAsPopulation = false) { filteredAccidents.Filters.Clear(); PropertyDescriptor pd = OrbitApplication.Instance.GetDescriptor(PFC.FixedCounty); PropertyFilter pf = new PropertyFilterXML(pd); pf.Relation = FilterRelation.equals; pf.AddValue(county); filteredAccidents.Filters.Clear(); filteredAccidents.Filters.Add(pf); PropertyDescriptor pdYear = OrbitApplication.Instance.GetDescriptor(PFC.FixedAccidentDate); if (yearFrom != null) { PropertyFilter pfYear = new PropertyFilterXML(pdYear); pfYear.Relation = FilterRelation.greaterorequal; pfYear.AddValue(new DateTime((int)yearFrom, 1, 1)); filteredAccidents.Filters.Add(pfYear); } if (yearTo != null) { PropertyFilter pfYear = new PropertyFilterXML(pdYear); pfYear.Relation = FilterRelation.lesserorequal; pfYear.AddValue(new DateTime((int)yearTo, 12, 31)); filteredAccidents.Filters.Add(pfYear); } if (skip9600) { PropertyFilter pfNoBuiltup = new PropertyFilterXML(OrbitApplication.Instance.GetDescriptor(PFC.FixedRoadName)); pfNoBuiltup.Relation = FilterRelation.notequals; pfNoBuiltup.AddValue("94000"); filteredAccidents.Filters.Add(pfNoBuiltup); } filteredAccidents.Load(); if (saveAsPopulation) population = ScoresOfAccidents(filteredAccidents); } public double WelchTest(BlackSpot blackSpot) { Sample p = new Sample(population); Sample bs = new Sample(ScoresOfAccidents(blackSpot.GetAccidents())); if (bs.Variance == 0) return StudentTest(blackSpot); TestResult tres = Sample.WelchTest(bs, p, TestType.RightTailed); return tres.Probability; } private List ScoresOfAccidents(List accidents) { List result = new List(); foreach (Accident acc in accidents) result.Add(ScoreOfAccident(acc)); return result; } private double ScoreOfAccident(Accident accident) { return rd.Evaluate(ReasonDetermination.ReasonType.Slippery, accident); } } public abstract class DerivedParameter { public abstract float Calculate(Accident acc); } public class DerivedSlipperyParameter : DerivedParameter { PropertyDescriptor naturePD = OrbitApplication.Instance.GetDescriptor(new Guid("00000000-0000-0000-0000-000000000043")); PropertyDescriptor roadSurfacePD = OrbitApplication.Instance.GetDescriptor(new Guid("00000000-0000-0000-0000-000000000115")); PropertyDescriptor weatherPD = OrbitApplication.Instance.GetDescriptor(new Guid("00000000-0000-0000-0000-000000000041")); public override float Calculate(Accident acc) { float score = 0; int nature = (int)acc[naturePD]; if (nature == 31) score += 0.6f; // megcsúszás, farolás, felborulás az útpályán if (nature == 32) score += 0.2f; // pályaelhagyás, szilárd tárgynak ütközés nélkül if (nature == 33) score += 0.2f; //pályaelhagyás, szilárd tárgynak ütközés az útpályán kívül int roadSurface = (int)acc[roadSurfacePD]; if (roadSurface == 2) score += 0.3f; // nedves if (roadSurface == 3) score += 0.5f; // havas, jeges if (roadSurface == 4) score += 0.8f; // olajos, csúszós if (roadSurface == 5) score += 0.2f; // egyéb szennyezett (sáros, hulladékos stb.) int weather = (int)acc[weatherPD]; if (weather == 4) score += 0.2f; // esős if (weather == 5) score += 0.3f; // viharos, zivataros if (weather == 6) score += 0.4f; // havazás return score; } } public class ReasonDetermination { public enum ReasonType { Slippery }; Dictionary reasons = new Dictionary() { {ReasonType.Slippery, new DerivedSlipperyParameter()} }; public float Evaluate(ReasonType reason, List accidents) { DerivedParameter dp = reasons[reason]; float sumScore = 0; foreach (Accident acc in accidents) { sumScore += dp.Calculate(acc); } return sumScore; } } public class BlackSpotItem : IEOVPosition { PureGPSPosition position; Accident accident; DBScanParameters param; public Accident Accident { get { return accident; } } public BlackSpotItem(Accident accident, DBScanParameters param, WeightCalculationHandler weightCalculator) { this.accident = accident; this.param = param; this.selectedWeightCalculator = weightCalculator; position = new PureGPSPosition() { Latitude = accident.Latitude, Longitude = accident.Longitude }; } public BlackSpotItem(int EOVX, int EOVY) // test { position = new PureGPSPosition() { EOVX = EOVX, EOVY = EOVY }; } #region IEOVPosition Members public int EOVX { get { return position.EOVX; } } public int EOVY { get { return position.EOVY; } } #endregion public float Weight { get { return selectedWeightCalculator(param, accident); } } WeightCalculationHandler selectedWeightCalculator; public delegate float WeightCalculationHandler(DBScanParameters param, Accident accident); public static float WeightModeClassic(DBScanParameters param, Accident accident) { switch (accident.Result) { case AccidentResult.Dead: return (float)param.IndicatorParameters.WeightDeadAccident; case AccidentResult.Hard: return (float)param.IndicatorParameters.WeightHardAccident; case AccidentResult.Light: return (float)param.IndicatorParameters.WeightLightAccident; case AccidentResult.Property: return (float)param.IndicatorParameters.WeightPropAccident; default: return 0; } } } public class DBScanParameters { public float GeoWindowSize { get; set; } public float GrowDistance { get; set; } public int MinimumCount { get; set; } public float MinimumDensity { get; set; } public float MinimumArea { get; set; } IndicatorParameters indicatorParameters; public IndicatorParameters IndicatorParameters { get { return indicatorParameters; } set { indicatorParameters = value; } } public DBScanParameters() { GeoWindowSize = 10000f; // default } } public class DBScanCluster : List { public DBScanCluster Clone() { DBScanCluster c = new DBScanCluster(param); c.AddRange(this); c.boundaryPoly = boundaryPoly.Clone(); c.sumDistance = sumDistance; c.sumWeight = sumWeight; return c; } internal DBScanParameters param; internal BoundaryPolygon boundaryPoly = new BoundaryPolygon(); float sumDistance = 0; float sumWeight = 0; public DBScanCluster(DBScanParameters param) : base() { this.param = param; } internal float Distance(BlackSpotItem acc) { return this.Min(x => DBScan.Distance(x, acc)); } internal float DistanceSq(BlackSpotItem acc) { return this.Min(x => DBScan.DistanceSq(x, acc)); } internal float Distance(DBScanCluster clu) { return clu.Min(y => this.Min(x => DBScan.Distance(x, y))); } public new void Add(BlackSpotItem acc) { if (this.Count != 0) { float dist = Distance(acc); sumDistance += dist; } base.Add(acc); boundaryPoly.Add(acc); } public float ExpectedScore(BlackSpotItem acc) { float accDist = Distance(acc); if (accDist > param.GrowDistance) return 0; float accDens; float expectedArea = boundaryPoly.ExpectedArea(acc) / 1000000; if (expectedArea > 0) { accDens = (sumWeight + acc.Weight) / expectedArea; } else { accDens = (sumWeight + acc.Weight) / (sumDistance + accDist) * 1000; } return accDens < param.MinimumDensity ? 0 : accDens; } public bool Acceptable { get { return this.Count >= param.MinimumCount && this.Score >= param.MinimumDensity; } } public float Score { get { float size = boundaryPoly.Area; if (size <= 0) size = sumDistance; return this.Sum(x => x.Weight) / Math.Max(param.MinimumArea, size); } } public bool IsOverlapping(DBScanCluster other) { foreach (BlackSpotItem bi in this) if (other.Contains(bi)) return true; return false; } } public class DBScan { public int ValidAccidentCount { get; set; } List accidents; DBScanParameters param; float windowLeft; float windowTop; float windowWidth; float windowHeight; List[,] GW; public static float Distance(BlackSpotItem a1, BlackSpotItem a2) { return (float)PureGPSPosition.EOVDistance(a1, a2); } public static float DistanceSq(BlackSpotItem a1, BlackSpotItem a2) { return (float)PureGPSPosition.EOVDistanceSq(a1, a2); } int[] Windowpos(BlackSpotItem acc) { int[] result = new int[2]; result[0] = (int)((acc.EOVY - windowLeft) / param.GeoWindowSize); result[1] = (int)((acc.EOVX - windowTop) / param.GeoWindowSize); return result; } public DBScan(Accidents accidentSet, DBScanParameters parameters) { this.param = parameters; this.accidents = new List(); foreach (Accident acc in accidentSet) { if (PureGPSPosition.InHungary(new PureGPSPosition() { Longitude = acc.Longitude, Latitude = acc.Latitude })) { this.accidents.Add(new BlackSpotItem(acc, param, BlackSpotItem.WeightModeClassic)); } } ValidAccidentCount = accidents.Count; if (ValidAccidentCount > 0) { windowLeft = (float)accidents.Min(x => x.EOVY); windowTop = (float)accidents.Min(x => x.EOVX); windowWidth = (float)accidents.Max(x => x.EOVY) - windowLeft; windowHeight = (float)accidents.Max(x => x.EOVX) - windowTop; int xsize = (int)(windowWidth / param.GeoWindowSize + 1); int ysize = (int)(windowHeight / param.GeoWindowSize + 1); GW = new List[xsize, ysize]; foreach (BlackSpotItem acc in accidents) { int[] pos = Windowpos(acc); if (GW[pos[0], pos[1]] == null) GW[pos[0], pos[1]] = new List(); GW[pos[0], pos[1]].Add(acc); } } } List neighbours; List loadedNeightbours; void CollectNeighbours(BlackSpotItem acc) { int[] swi = Windowpos(acc); for (int x = Math.Max(0, swi[0] - 1); x <= Math.Min(GW.GetLength(0) - 1, swi[0] + 1); x++) for (int y = Math.Max(0, swi[1] - 1); y <= Math.Min(GW.GetLength(1) - 1, swi[1] + 1); y++) if (GW[x, y] != null && !loadedNeightbours.Exists(r => r[0] == x && r[1] == y)) { loadedNeightbours.Add(new int[] { x, y }); foreach (BlackSpotItem acr in GW[x, y]) { if (acr != acc) neighbours.Add(acr); } } } DBScanCluster StartGrowing(BlackSpotItem first) { neighbours = new List(); loadedNeightbours = new List(); DBScanCluster cluster = new DBScanCluster(param); cluster.Add(first); CollectNeighbours(first); DBScanCluster acceptableClone = null; BlackSpotItem bestAcc = null; do { // Search by area bestAcc = null; float bestAccScore = 0; foreach (BlackSpotItem acc in neighbours) { float expScore = cluster.ExpectedScore(acc); if (expScore > bestAccScore) { bestAcc = acc; bestAccScore = expScore; } } if (bestAcc != null) { cluster.Add(bestAcc); if (cluster.Acceptable) acceptableClone = cluster.Clone(); neighbours.Remove(bestAcc); CollectNeighbours(bestAcc); } } while (bestAcc != null); if (acceptableClone != null) { acceptableClone.boundaryPoly.ReCalculateArea(); return acceptableClone; } else return null; } void RemoveOverlappingClusters(List clusters) { clusters.Sort(delegate (DBScanCluster a, DBScanCluster b) { return -a.Score.CompareTo(b.Score); }); int i = 1; while (i < clusters.Count) { int j = 0; while (j < i && !clusters[i].IsOverlapping(clusters[j])) j++; if (j < i) clusters.RemoveAt(i); else i++; } } public List Scan() { List result = new List(); foreach (BlackSpotItem acc in accidents) { DBScanCluster res = StartGrowing(acc); if (res != null) result.Add(res); } RemoveOverlappingClusters(result); return result; } public List GetBlackSpotList(IUser owner) { List result = new List(); List list = Scan(); foreach (DBScanCluster cluster in list) { BlackSpot bs = (BlackSpot)Orbit.Service.OrbitApplication.Instance.BlackSpots.CreateNewBlackSpot(owner); //TODO: castolas nem kellene bs.BoundingPolygon = cluster.boundaryPoly; bs.UsedMethod = BlackSpotMethodType.DBSCAN; bs.IndicatorParameters = param.IndicatorParameters; foreach (BlackSpotItem ci in cluster) { bs.AddAccident(ci.Accident); } bs.AreaDensity = Math.Round(cluster.Score, 6); bs.DbScanArea = cluster.boundaryPoly.Area; bs.DbScanCorrectedArea = Math.Max(cluster.param.MinimumArea, cluster.boundaryPoly.Area); result.Add(bs); } return result; } } public class DBScanBlackSpotSearch { public int ValidAccidentCount { get; set; } IUser user; public IUser User { get { return user; } set { user = value; } } DBScanParameters parameters; DBScan dbscan; public DBScanBlackSpotSearch(Accidents accidentSet, DBScanParameters parameters) { this.parameters = parameters; dbscan = new DBScan(accidentSet, parameters); ValidAccidentCount = dbscan.ValidAccidentCount; } public List SearchBlackSpots() { return dbscan.GetBlackSpotList(user); } } }