OSMServer/RenderPath/Renderer.cs
glax 3077b4d8b8 Added abstract Renderer class that SVGRenderer and PNGRenderer inherit from.
This way standardized rendering methods can be implemented.
2023-05-16 20:00:18 +02:00

194 lines
7.8 KiB
C#

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<OsmNode> 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> 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<OsmNode, double> 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<OsmNode, double> 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));
}