using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using OSMDatastructure.Graph; using Pathfinding; namespace RenderPath; public static class Renderer { private const int ImageMaxSize = 20000; private const float PenThickness = 4; private static readonly Color RouteColor = Color.Red; private static readonly Color WeightStartColor = Color.FromArgb(0, 0, 255); private static readonly Color WeightCenterColor = Color.FromArgb(255, 255, 0); private static readonly Color WeightEndColor = Color.FromArgb(0, 255, 0); public class Bounds { public readonly float minLat, maxLat, minLon, maxLon; public Bounds(float minLat, float minLon, float maxLat, float maxLon) { this.minLon = minLon; this.maxLat = maxLat; this.maxLon = maxLon; this.minLat = minLat; } } [SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")] public static Image DrawPathfinder(Pathfinder pathfinder) { Console.WriteLine("Rendering loaded Regions"); ValueTuple areaRender = DrawArea(pathfinder.regionManager); Console.WriteLine("Rendering gScores (Weights)"); ValueTuple areaGScoreRender = DrawGScores(pathfinder.gScore!, areaRender.Item1, areaRender.Item2); Console.WriteLine("Rendering path"); ValueTuple areaGScorePathRender = DrawPath(pathfinder.pathResult!, areaGScoreRender.Item1, areaGScoreRender.Item2); return areaGScorePathRender.Item1; } [SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")] public static ValueTuple DrawArea(RegionManager rm) { HashSet nodes = new(); foreach (OSMDatastructure.Region r in rm.GetAllRegions()) nodes = nodes.Concat(r.nodes).ToHashSet(); float minLat = nodes.Min(node => node.coordinates.latitude); float minLon = nodes.Min(node => node.coordinates.longitude); float maxLat = nodes.Max(node => node.coordinates.latitude); float maxLon = nodes.Max(node => node.coordinates.longitude); float latDiff = maxLat - minLat; float lonDiff = maxLon - minLon; float scaleFactor = latDiff > lonDiff ? ImageMaxSize / latDiff : ImageMaxSize / lonDiff; int pixelsX = (int)(lonDiff * scaleFactor); int pixelsY = (int)(latDiff * scaleFactor); Image ret = new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppRgb); Graphics g = Graphics.FromImage(ret); g.Clear(Color.White); //TODO Use road priority for roadcolor Color start = Color.FromArgb(255, 25, 25, 25); Color center = Color.FromArgb(255, 0, 0, 0); Color end = Color.FromArgb(255, 0, 255, 0); foreach (OsmNode node in nodes) { foreach (OsmEdge edge in node.edges) { Coordinates c1 = node.coordinates; OsmNode nNode = rm.GetNode(edge.neighborId, edge.neighborRegion)!; Coordinates c2 = nNode.coordinates; Pen p = new(GradientPick(0, start, center, end), PenThickness); float x1 = (c1.longitude - minLon) * scaleFactor; float y1 = (maxLat - c1.latitude) * scaleFactor; float x2 = (c2.longitude - minLon) * scaleFactor; float y2 = (maxLat - c2.latitude) * scaleFactor; g.DrawLine(p, x1, y1, x2, y2); } } return new ValueTuple(ret, new Bounds(minLat,minLon,maxLat,maxLon)); } [SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")] public static ValueTuple DrawPath(PathResult pathResult, Image? renderOver = null, Bounds? bounds = null) { List coordinates = new(); foreach(PathNode node in pathResult.pathNodes) coordinates.Add(node.coordinates); float minLat = bounds?.minLat ?? coordinates.Min(coords => coords.latitude); float minLon = bounds?.minLon ?? coordinates.Min(coords => coords.longitude); float maxLat = bounds?.maxLat ?? coordinates.Max(coords => coords.latitude); float maxLon = bounds?.maxLon ?? coordinates.Max(coords => coords.longitude); float latDiff = maxLat - minLat; float lonDiff = maxLon - minLon; float scaleFactor = latDiff > lonDiff ? ImageMaxSize / latDiff : ImageMaxSize / lonDiff; int pixelsX = (int)(lonDiff * scaleFactor); int pixelsY = (int)(latDiff * scaleFactor); Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppRgb); Graphics g = Graphics.FromImage(ret); if(renderOver is null) g.Clear(Color.White); Pen p = new Pen(RouteColor, PenThickness); for (int i = 0; i < coordinates.Count - 1; i++) { Coordinates c1 = coordinates[i]; Coordinates c2 = coordinates[i + 1]; Point p1 = new(Convert.ToInt32((c1.longitude - minLon) * scaleFactor), Convert.ToInt32((maxLat - c1.latitude) * scaleFactor)); Point p2 = new(Convert.ToInt32((c2.longitude - minLon) * scaleFactor), Convert.ToInt32((maxLat - c2.latitude) * scaleFactor)); g.DrawLine(p, p1, p2); } return new ValueTuple(ret, new Bounds(minLat,minLon,maxLat,maxLon)); } [SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")] public static ValueTuple DrawGScores(Dictionary gScoreDict, Image? renderOver = null, Bounds? bounds = null) { float minLat = bounds?.minLat ?? gScoreDict.Min(kv => kv.Key.coordinates.latitude); float minLon = bounds?.minLon ?? gScoreDict.Min(kv => kv.Key.coordinates.longitude); float maxLat = bounds?.maxLat ?? gScoreDict.Max(kv => kv.Key.coordinates.latitude); float maxLon = bounds?.maxLon ?? gScoreDict.Max(kv => kv.Key.coordinates.longitude); double minWeight = gScoreDict.Min(kv => kv.Value); double maxWeight = gScoreDict.Max(kv => kv.Value); float latDiff = maxLat - minLat; float lonDiff = maxLon - minLon; float scaleFactor = latDiff > lonDiff ? ImageMaxSize / latDiff : ImageMaxSize / lonDiff; int pixelsX = (int)(lonDiff * scaleFactor); int pixelsY = (int)(latDiff * scaleFactor); Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppRgb); Graphics g = Graphics.FromImage(ret); if(renderOver is null) g.Clear(Color.White); foreach (KeyValuePair kv in gScoreDict) { double percentage = (kv.Value - minWeight) / (maxWeight - minWeight); Brush b = new SolidBrush(GradientPick(percentage, WeightStartColor, WeightCenterColor, WeightEndColor)); float x = (kv.Key.coordinates.longitude - minLon) * scaleFactor; float y = (maxLat - kv.Key.coordinates.latitude) * scaleFactor; x -= (PenThickness * 1.5f) / 2; y -= (PenThickness * 1.5f) / 2; g.FillEllipse(b, x, y, PenThickness * 1.5f, PenThickness * 1.5f); } return new ValueTuple(ret, new Bounds(minLat,minLon,maxLat,maxLon)); } /* * https://stackoverflow.com/questions/55601338/get-a-color-value-within-a-gradient-based-on-a-value */ private static int LinearInterp(int start, int end, double percentage) => start + (int)Math.Round(percentage * (end - start)); private static Color ColorInterp(Color start, Color end, double percentage) => Color.FromArgb(LinearInterp(start.A, end.A, percentage), LinearInterp(start.R, end.R, percentage), LinearInterp(start.G, end.G, percentage), LinearInterp(start.B, end.B, percentage)); private static Color GradientPick(double percentage, Color Start, Color Center, Color End) { if (percentage < 0.5) return ColorInterp(Start, Center, percentage / 0.5); else if (percentage == 0.5) return Center; else return ColorInterp(Center, End, (percentage - 0.5)/0.5); } }