Added abstract Renderer class that SVGRenderer and PNGRenderer inherit from.

This way standardized rendering methods can be implemented.
This commit is contained in:
2023-05-16 20:00:18 +02:00
parent 7c5d87ca76
commit 3077b4d8b8
4 changed files with 284 additions and 95 deletions

44
RenderPath/PNGRenderer.cs Normal file
View File

@ -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

View File

@ -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<Image, Bounds> areaRender = DrawArea(pathfinder.regionManager);
Console.WriteLine("Rendering gScores (Weights)");
ValueTuple<Image, Bounds> areaGScoreRender = DrawGScores(pathfinder.gScore!, areaRender.Item1, areaRender.Item2);
Console.WriteLine("Rendering path");
ValueTuple<Image, Bounds> areaGScorePathRender = DrawPath(pathfinder.pathResult!, areaGScoreRender.Item1, areaGScoreRender.Item2);
public Bounds? bounds;
return areaGScorePathRender.Item1;
}
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
public static ValueTuple<Image, Bounds> 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<OsmNode> 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<Image, Bounds>(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<Image, Bounds> DrawPath(PathResult pathResult, Image? renderOver = null, Bounds? bounds = null)
public static Renderer DrawPath(PathResult pathResult, RenderType renderType, Renderer? drawOver)
{
List<Coordinates> 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<Image, Bounds>(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<Image, Bounds> DrawGScores(Dictionary<OsmNode, double> gScore, Image? renderOver = null,
Bounds? bounds = null)
public static Renderer DrawGScores(Dictionary<OsmNode, double> 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<OsmNode, double> 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<Image, Bounds>(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
*/

92
RenderPath/SVGRenderer.cs Normal file
View File

@ -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}";
}
}