using System.Drawing; using OSMDatastructure; using OSMDatastructure.Graph; using Pathfinding; namespace RenderPath; public abstract class Renderer { private const int ImageMaxSize = 20000; private const int PenThickness = 2; private static readonly Color RouteColor = Color.Red; private static readonly Color WeightStartColor = Color.FromArgb(127, 0, 100, 255); private static readonly Color WeightEndColor = Color.FromArgb(255, 0, 255, 0); private static readonly Color RoadPrioStart = Color.FromArgb(200, 100, 100, 100); private static readonly Color RoadPrioEnd = Color.FromArgb(255, 255, 180, 0); public Bounds? bounds; public enum RenderType { png, svg} public abstract void DrawLine(float x1, float y1, float x2, float y2, int width, Color color); public abstract void DrawDot(float x, float y, int r, Color color); public abstract void Save(string path); public static Renderer DrawArea(RegionManager rm, RenderType renderType) { 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); Renderer renderer; switch (renderType) { case RenderType.svg: renderer = new SVGRenderer(pixelsX, pixelsY); break; default: renderer = new PNGRenderer(pixelsX, pixelsY); break; } foreach (OsmNode node in nodes) { foreach (OsmEdge edge in node.edges) { double priority = rm.GetPriorityForVehicle(Tag.SpeedType.car, edge, node) / 20; Coordinates c1 = node.coordinates; OsmNode nNode = rm.GetNode(edge.neighborId, edge.neighborRegion)!; Coordinates c2 = nNode.coordinates; float x1 = (c1.longitude - minLon) * scaleFactor; float y1 = (maxLat - c1.latitude) * scaleFactor; float x2 = (c2.longitude - minLon) * scaleFactor; float y2 = (maxLat - c2.latitude) * scaleFactor; renderer.DrawLine(x1, y1, x2, y2, PenThickness, ColorInterp(RoadPrioStart, RoadPrioEnd, priority)); } } renderer.bounds = new Bounds(minLat,minLon,maxLat,maxLon); return renderer; } public static Renderer DrawPath(PathResult pathResult, RenderType renderType, Renderer? drawOver) { List coordinates = new(); foreach(PathNode node in pathResult.pathNodes) coordinates.Add(node.coordinates); float minLat = drawOver?.bounds!.minLat ?? coordinates.Min(coords => coords.latitude); float minLon = drawOver?.bounds!.minLon ?? coordinates.Min(coords => coords.longitude); float maxLat = drawOver?.bounds!.maxLat ?? coordinates.Max(coords => coords.latitude); float maxLon = drawOver?.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); Renderer renderer; if(drawOver is null) switch (renderType) { case RenderType.svg: renderer = new SVGRenderer(pixelsX, pixelsY); break; default: renderer = new PNGRenderer(pixelsX, pixelsY); break; } else renderer = drawOver; for (int i = 0; i < coordinates.Count - 1; i++) { Coordinates c1 = coordinates[i]; Coordinates c2 = coordinates[i + 1]; float x1 = (c1.longitude - minLon) * scaleFactor; float y1 = (maxLat - c1.latitude) * scaleFactor; float x2 = (c2.longitude - minLon) * scaleFactor; float y2 = (maxLat - c2.latitude) * scaleFactor; renderer.DrawLine(x1, y1, x2, y2, PenThickness, RouteColor); } renderer.bounds = new Bounds(minLat, minLon, maxLat, maxLon); return renderer; } public static Renderer DrawGScores(Dictionary gScore, RenderType renderType, Renderer? drawOver) { float minLat = drawOver?.bounds!.minLat ?? gScore.Min(kv => kv.Key.coordinates.latitude); float minLon = drawOver?.bounds!.minLon ?? gScore.Min(kv => kv.Key.coordinates.longitude); float maxLat = drawOver?.bounds!.maxLat ?? gScore.Max(kv => kv.Key.coordinates.latitude); float maxLon = drawOver?.bounds!.maxLon ?? gScore.Max(kv => kv.Key.coordinates.longitude); double minWeight = gScore.Min(kv => kv.Value); double maxWeight = gScore.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); Renderer renderer; if(drawOver is null) switch (renderType) { case RenderType.svg: renderer = new SVGRenderer(pixelsX, pixelsY); break; default: renderer = new PNGRenderer(pixelsX, pixelsY); break; } else renderer = drawOver; foreach (KeyValuePair kv in gScore) { double percentage = (kv.Value - minWeight) / (maxWeight - minWeight); float x = (kv.Key.coordinates.longitude - minLon) * scaleFactor; float y = (maxLat - kv.Key.coordinates.latitude) * scaleFactor; renderer.DrawDot(x, y, PenThickness, ColorInterp(WeightStartColor, WeightEndColor, percentage)); } renderer.bounds = new Bounds(minLat,minLon,maxLat,maxLon); return renderer; } public static Renderer DrawPathfinder(Pathfinder pathfinder, RenderType renderType) { Console.WriteLine("Rendering loaded Regions"); Renderer areaRender = DrawArea(pathfinder.regionManager, renderType); Console.WriteLine("Rendering gScores (Weights)"); Renderer areaGScoreRender = DrawGScores(pathfinder.gScore!, renderType, areaRender); Console.WriteLine("Rendering path"); Renderer areaGScorePathRender = DrawPath(pathfinder.pathResult!, renderType, areaGScoreRender); return areaGScorePathRender; } /* * 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)); }