From 3077b4d8b8a6b36ec5ac70c0f6b93f0283147460 Mon Sep 17 00:00:00 2001 From: glax Date: Tue, 16 May 2023 20:00:18 +0200 Subject: [PATCH] Added abstract Renderer class that SVGRenderer and PNGRenderer inherit from. This way standardized rendering methods can be implemented. --- RenderPath/PNGRenderer.cs | 44 +++++++++++ RenderPath/Renderer.cs | 154 +++++++++++++++++++++----------------- RenderPath/SVGRenderer.cs | 92 +++++++++++++++++++++++ Server/Server.cs | 89 +++++++++++++++------- 4 files changed, 284 insertions(+), 95 deletions(-) create mode 100644 RenderPath/PNGRenderer.cs create mode 100644 RenderPath/SVGRenderer.cs diff --git a/RenderPath/PNGRenderer.cs b/RenderPath/PNGRenderer.cs new file mode 100644 index 0000000..06a8543 --- /dev/null +++ b/RenderPath/PNGRenderer.cs @@ -0,0 +1,44 @@ +using System.Drawing; +using System.Drawing.Imaging; + +namespace RenderPath; + +#pragma warning disable CA1416 +public class PNGRenderer : Renderer +{ + private readonly Image _image; + private readonly Graphics _graphics; + + public PNGRenderer(int width, int height) + { + _image = new Bitmap(width, height, PixelFormat.Format32bppPArgb); + _graphics = Graphics.FromImage(_image); + _graphics.Clear(Color.White); + } + + public PNGRenderer(Image renderOver) + { + _image = renderOver; + _graphics = Graphics.FromImage(renderOver); + } + + public override void DrawLine(float x1, float y1, float x2, float y2, int width, Color color) + { + Pen p = new Pen(color, width); + _graphics.DrawLine(p, x1, y1, x2, y2); + } + + public override void DrawDot(float x, float y, int radius, Color color) + { + Brush b = new SolidBrush(color); + x -= radius / 2f; + y -= radius / 2f; + _graphics.FillEllipse(b, x, y, radius, radius); + } + + public override void Save(string path) + { + _image.Save($"{path}.png", ImageFormat.Png); + } +} +#pragma warning restore CA1416 \ No newline at end of file diff --git a/RenderPath/Renderer.cs b/RenderPath/Renderer.cs index ecf3bbd..2a05d51 100644 --- a/RenderPath/Renderer.cs +++ b/RenderPath/Renderer.cs @@ -1,38 +1,29 @@ -using System.Diagnostics.CodeAnalysis; using System.Drawing; -using System.Drawing.Imaging; -using System.Text.Json.Serialization; using OSMDatastructure; using OSMDatastructure.Graph; using Pathfinding; namespace RenderPath; -public static class Renderer +public abstract class Renderer { private const int ImageMaxSize = 20000; - private const float PenThickness = 2; + 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 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); - [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); + public Bounds? bounds; - return areaGScorePathRender.Item1; - } - - [SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")] - public static ValueTuple DrawArea(RegionManager rm) + 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()) @@ -50,10 +41,17 @@ public static class Renderer int pixelsX = (int)(lonDiff * scaleFactor); int pixelsY = (int)(latDiff * scaleFactor); - - Image ret = new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppPArgb); - Graphics g = Graphics.FromImage(ret); - g.Clear(Color.White); + + 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) { @@ -64,30 +62,29 @@ public static class Renderer OsmNode nNode = rm.GetNode(edge.neighborId, edge.neighborRegion)!; Coordinates c2 = nNode.coordinates; - Pen p = new(ColorInterp(RoadPrioStart, RoadPrioEnd, priority), 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); + renderer.DrawLine(x1, y1, x2, y2, PenThickness, ColorInterp(RoadPrioStart, RoadPrioEnd, priority)); } } - return new ValueTuple(ret, new Bounds(minLat,minLon,maxLat,maxLon)); + renderer.bounds = new Bounds(minLat,minLon,maxLat,maxLon); + return renderer; } - - [SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")] - public static ValueTuple DrawPath(PathResult pathResult, Image? renderOver = null, Bounds? bounds = null) + + 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 = 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 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; @@ -97,36 +94,43 @@ public static class Renderer int pixelsX = (int)(lonDiff * scaleFactor); int pixelsY = (int)(latDiff * scaleFactor); - Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppPArgb); - Graphics g = Graphics.FromImage(ret); - if(renderOver is null) - g.Clear(Color.White); - - Pen p = new Pen(RouteColor, PenThickness); + 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]; - 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); + 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); } - return new ValueTuple(ret, new Bounds(minLat,minLon,maxLat,maxLon)); + renderer.bounds = new Bounds(minLat, minLon, maxLat, maxLon); + return renderer; } - - [SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")] - public static ValueTuple DrawGScores(Dictionary gScore, Image? renderOver = null, - Bounds? bounds = null) + + public static Renderer DrawGScores(Dictionary gScore, RenderType renderType, Renderer? drawOver) { - float minLat = bounds?.minLat ?? gScore.Min(kv => kv.Key.coordinates.latitude); - float minLon = bounds?.minLon ?? gScore.Min(kv => kv.Key.coordinates.longitude); - float maxLat = bounds?.maxLat ?? gScore.Max(kv => kv.Key.coordinates.latitude); - float maxLon = bounds?.maxLon ?? gScore.Max(kv => kv.Key.coordinates.longitude); + 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); @@ -139,29 +143,45 @@ public static class Renderer int pixelsX = (int)(lonDiff * scaleFactor); int pixelsY = (int)(latDiff * scaleFactor); - Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppPArgb); - Graphics g = Graphics.FromImage(ret); - if(renderOver is null) - g.Clear(Color.White); + 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; - float pointSize = PenThickness * 1.5f; - foreach (KeyValuePair kv in gScore) { double percentage = (kv.Value - minWeight) / (maxWeight - minWeight); - Brush b = new SolidBrush(ColorInterp(WeightStartColor, WeightEndColor, percentage)); - float x = (kv.Key.coordinates.longitude - minLon) * scaleFactor; float y = (maxLat - kv.Key.coordinates.latitude) * scaleFactor; - - x -= pointSize / 2; - y -= pointSize / 2; - g.FillEllipse(b, x, y, pointSize, pointSize); + + renderer.DrawDot(x, y, PenThickness, ColorInterp(WeightStartColor, WeightEndColor, percentage)); } - return new ValueTuple(ret, new Bounds(minLat,minLon,maxLat,maxLon)); + 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 */ diff --git a/RenderPath/SVGRenderer.cs b/RenderPath/SVGRenderer.cs new file mode 100644 index 0000000..c7e36ef --- /dev/null +++ b/RenderPath/SVGRenderer.cs @@ -0,0 +1,92 @@ +using System.Drawing; + +namespace RenderPath; +using System.Xml; + +public class SVGRenderer : Renderer +{ + private readonly XmlDocument _image; + private XmlElement _document; + + public SVGRenderer(int width, int height) + { + _image = new XmlDocument(); + CreateTree(width, height); + } + + public SVGRenderer(XmlDocument renderOver) + { + _image = renderOver; + _document = _image.GetElementById("svg")!; + } + + private void CreateTree(int width, int height) + { + XmlDeclaration xmlDeclaration = _image.CreateXmlDeclaration( "1.0", "UTF-8", null ); + _image.InsertBefore(xmlDeclaration, _image.DocumentElement); + XmlElement pElement = _image.CreateElement("svg"); + XmlAttribute xmlns = _image.CreateAttribute("xmlns"); + xmlns.Value = "http://www.w3.org/2000/svg"; + pElement.Attributes.Append(xmlns); + XmlAttribute aWidth = _image.CreateAttribute("width"); + aWidth.Value = width.ToString(); + pElement.Attributes.Append(aWidth); + XmlAttribute aHeight = _image.CreateAttribute("height"); + aHeight.Value = height.ToString(); + pElement.Attributes.Append(aHeight); + _image.AppendChild(pElement); + _document = pElement; + } + + public override void DrawLine(float x1, float y1, float x2, float y2, int width, Color color) + { + XmlElement newLine = _image.CreateElement("line"); + XmlAttribute aX1 = _image.CreateAttribute("x1"); + aX1.Value = Math.Floor(x1).ToString("0"); + newLine.Attributes.Append(aX1); + XmlAttribute aY1 = _image.CreateAttribute("y1"); + aY1.Value = Math.Floor(y1).ToString("0"); + newLine.Attributes.Append(aY1); + XmlAttribute aX2 = _image.CreateAttribute("x2"); + aX2.Value = Math.Floor(x2).ToString("0"); + newLine.Attributes.Append(aX2); + XmlAttribute aY2 = _image.CreateAttribute("y2"); + aY2.Value = Math.Floor(y2).ToString("0"); + newLine.Attributes.Append(aY2); + XmlAttribute stroke = _image.CreateAttribute("stroke-width"); + stroke.Value = width.ToString(); + newLine.Attributes.Append(stroke); + XmlAttribute aColor = _image.CreateAttribute("stroke"); + aColor.Value = HexFromColor(color); + newLine.Attributes.Append(aColor); + _document.AppendChild(newLine); + } + + public override void DrawDot(float x, float y, int radius, Color color) + { + XmlElement newCircle = _image.CreateElement("circle"); + XmlAttribute aX = _image.CreateAttribute("cx"); + aX.Value = Math.Floor(x).ToString("0"); + newCircle.Attributes.Append(aX); + XmlAttribute aY = _image.CreateAttribute("cy"); + aY.Value = Math.Floor(y).ToString("0"); + newCircle.Attributes.Append(aY); + XmlAttribute aR = _image.CreateAttribute("r"); + aR.Value = radius.ToString(); + newCircle.Attributes.Append(aR); + XmlAttribute fill = _image.CreateAttribute("fill"); + fill.Value = HexFromColor(color); + newCircle.Attributes.Append(fill); + _document.AppendChild(newCircle); + } + + public override void Save(string path) + { + _image.Save($"{path}.svg"); + } + + private static string HexFromColor(Color color) + { + return $"#{color.R:X2}{color.G:X2}{color.B:X2}"; + } +} \ No newline at end of file diff --git a/Server/Server.cs b/Server/Server.cs index eb09682..2deeb4f 100644 --- a/Server/Server.cs +++ b/Server/Server.cs @@ -1,5 +1,7 @@ +using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; +using System.Text.Json; using OSMDatastructure; using OSMDatastructure.Graph; using Pathfinding; @@ -11,6 +13,7 @@ namespace Server; public class Server { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public static void Main(string[] args) { ConsoleWriter newConsole = new(); @@ -25,27 +28,34 @@ public class Server Coordinates start = new (48.7933798f, 9.8275859f); Coordinates finish = new (48.795918f, 9.021618f); - - TestVariables(workingDir, start, finish, 16); - GetShortestRoute("D:"); + Pathfinder result = new Pathfinder(workingDir, 2, 30).AStar(start, finish, Tag.SpeedType.car, 1); + Renderer image = Renderer.DrawPathfinder(result, Renderer.RenderType.png); + image.Save("D:/stuttgart-regbez-latest"); + /* - ValueTuple area = Renderer.DrawArea(LoadRegions(workingDir, start, finish)); - area.Item1.Save(@"D:\Base.png", ImageFormat.Png); + if(File.Exists(@"D:\bounds")) + File.Delete(@"D:\bounds"); + RegionManager rm = LoadRegions(workingDir, start, finish); + Renderer areaRender = Renderer.DrawArea(rm, Renderer.RenderType.PNG); + FileStream s = new(@"D:\bounds", FileMode.OpenOrCreate); + JsonSerializer.Serialize(s, areaRender.bounds, JsonSerializerOptions.Default); + areaRender.Save(@"D:\Base"); + s.Dispose(); + */ - ValueTuple areaDistance = Renderer.DrawPath( - PathResult.PathresultFromFile(@"D:\angle0,140_level0,020_same0,160.result"), Image.FromFile(@"D:\Base.png"), area.Item2); - areaDistance.Item1.Save(@"D:\Distance.png", ImageFormat.Png); - - ValueTuple areaWeight = Renderer.DrawPath( - PathResult.PathresultFromFile(@"D:\angle0,160_level0,020_same0,020.result"), Image.FromFile(@"D:\Base.png"), area.Item2); - areaWeight.Item1.Save(@"D:\Weight.png", ImageFormat.Png); -*/ + + + //TestVariables(workingDir, start, finish, 12); + //GetShortestRoute("D:"); + /* string parentFolder = new DirectoryInfo(workingDir).Parent!.FullName; + Renderer.Bounds bounds = JsonSerializer.Deserialize(new FileStream(@"D:\bounds", FileMode.Open)); + Image baseImage = Image.FromFile(@"D:\Base.png"); - Pathfinder result = new Pathfinder(workingDir, 0.0215, 30).AStar(start, - finish, Tag.SpeedType.car, 3); + Pathfinder result = new Pathfinder(workingDir, 2, 30).AStar(start, + finish, Tag.SpeedType.car, 4); Console.WriteLine($"Calc-time {result.pathResult!.calcTime} Path-length: {result.pathResult.pathNodes.Count} Visited-nodes: {result.gScore!.Count}"); @@ -55,11 +65,15 @@ public class Server result.SaveResult(Path.Join(parentFolder, resultFileName)); string renderFileName = $"{new DirectoryInfo(workingDir).Name}-{fileName}.render.png"; - Image render = Renderer.DrawPathfinder(result); -#pragma warning disable CA1416 + + Image renderWeights = Renderer.DrawGScores(result.gScore, baseImage, bounds).Item1; + Image render = Renderer.DrawPath(result.pathResult, renderWeights, bounds).Item1; render.Save(Path.Join(parentFolder, renderFileName), ImageFormat.Png); -#pragma warning restore CA1416*/ - Console.Beep(400, 100); +*/ + Console.Beep(400, 50); + Console.Beep(600, 50); + Console.Beep(400, 50); + Console.Beep(600, 50); } private static void GetShortestRoute(string directory) @@ -93,7 +107,7 @@ public class Server float maxLon = c1.longitude > c2.longitude ? c1.longitude : c2.longitude; RegionManager allRegions = new(workingDir); - for (float lat = minLat - Region.RegionSize; lat < maxLat + Region.RegionSize; lat += Region.RegionSize / 2) + for (float lat = minLat - Region.RegionSize * 3; lat < maxLat + Region.RegionSize * 3; lat += Region.RegionSize / 2) { for (float lon = minLon - Region.RegionSize; lon < maxLon + Region.RegionSize; lon += Region.RegionSize / 2) { @@ -104,6 +118,7 @@ public class Server return allRegions; } + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] private static void TestVariables(string workingDir, Coordinates start, Coordinates finish, int threads) { string parentFolder = new DirectoryInfo(workingDir).Parent!.FullName; @@ -111,17 +126,34 @@ public class Server RegionManager rm = LoadRegions(workingDir, start, finish); Queue calcThreads = new(); + + Bounds bounds = JsonSerializer.Deserialize(new FileStream(@"D:\bounds", FileMode.Open))!; - for (double extraTime = 30; extraTime > 1; extraTime -= 1) + + for (double extraTime = 1.5; extraTime >= 1; extraTime -= 0.25) { - double time = extraTime; - calcThreads.Enqueue(new Thread(() => + for (double roadFactor = 0.05; roadFactor < 5; roadFactor += 0.05) { - Pathfinder testresult = new Pathfinder(workingDir, 0.0215, 30).AStar(start, - finish, Tag.SpeedType.car, time); - string fileName = $"time{time:0}.result"; - testresult.SaveResult(Path.Join(parentFolder, fileName)); - })); + double road = roadFactor; + double time = extraTime; + calcThreads.Enqueue(new Thread(() => + { + Pathfinder testresult = new Pathfinder(workingDir, road, 30).AStar(start, + finish, Tag.SpeedType.car, time); + Image baseImage = Image.FromStream(new FileStream(@"D:\Base.png", FileMode.Open, FileAccess.Read, FileShare.Read, + (int)new FileInfo(@"D:\Base.png").Length, FileOptions.Asynchronous)); + Renderer renderer = new PNGRenderer(baseImage); + renderer.bounds = bounds; + Renderer renderWeights = Renderer.DrawGScores(testresult.gScore!, Renderer.RenderType.png, renderer); + Renderer render = Renderer.DrawPath(testresult.pathResult!, Renderer.RenderType.png, renderWeights); + string fileName = $"road{road:0.00}_time{time:0.00}"; + string resultFileName = Path.Combine("D:", $"{fileName}.result"); + testresult.SaveResult(resultFileName); + string imageFileName = Path.Combine("D:", fileName); + render.Save(imageFileName); + Console.WriteLine($"Saved {fileName}"); + })); + } } int totalTasks = calcThreads.Count; @@ -129,6 +161,7 @@ public class Server DateTime startTime = DateTime.Now; HashSet runningThreads = new(); + Console.WriteLine($"Running {threads} Threads on {totalTasks} Tasks."); while (calcThreads.Count > 0 || runningThreads.Count > 0) { while (runningThreads.Count < threads && calcThreads.Count > 0)