2023-04-13 00:24:33 +02:00
|
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
|
using System.Drawing;
|
|
|
|
|
using System.Drawing.Imaging;
|
2023-04-21 14:37:38 +02:00
|
|
|
|
using OSMDatastructure;
|
2023-04-13 00:24:33 +02:00
|
|
|
|
using OSMDatastructure.Graph;
|
2023-04-13 19:18:25 +02:00
|
|
|
|
using Pathfinding;
|
2023-04-13 00:24:33 +02:00
|
|
|
|
|
|
|
|
|
namespace RenderPath;
|
|
|
|
|
|
2023-04-19 22:53:49 +02:00
|
|
|
|
public static class Renderer
|
2023-04-13 00:24:33 +02:00
|
|
|
|
{
|
2023-04-19 22:53:49 +02:00
|
|
|
|
private const int ImageMaxSize = 20000;
|
2023-04-23 13:06:47 +02:00
|
|
|
|
private const float PenThickness = 2;
|
2023-04-20 19:40:50 +02:00
|
|
|
|
private static readonly Color RouteColor = Color.Red;
|
2023-04-23 13:06:47 +02:00
|
|
|
|
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);
|
2023-04-21 14:37:38 +02:00
|
|
|
|
private static readonly Color RoadPrioEnd = Color.FromArgb(255, 255, 180, 0);
|
2023-04-13 01:00:56 +02:00
|
|
|
|
|
2023-04-19 22:53:49 +02:00
|
|
|
|
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")]
|
2023-04-20 19:40:50 +02:00
|
|
|
|
public static Image DrawPathfinder(Pathfinder pathfinder)
|
2023-04-13 19:18:25 +02:00
|
|
|
|
{
|
2023-04-20 19:40:50 +02:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
return areaGScorePathRender.Item1;
|
2023-04-13 19:18:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
|
2023-04-19 22:53:49 +02:00
|
|
|
|
public static ValueTuple<Image, Bounds> DrawArea(RegionManager rm)
|
2023-04-13 00:24:33 +02:00
|
|
|
|
{
|
2023-04-21 00:41:18 +02:00
|
|
|
|
HashSet<OsmNode> nodes = new();
|
2023-04-19 22:53:49 +02:00
|
|
|
|
foreach (OSMDatastructure.Region r in rm.GetAllRegions())
|
|
|
|
|
nodes = nodes.Concat(r.nodes).ToHashSet();
|
|
|
|
|
|
2023-04-13 19:18:25 +02:00
|
|
|
|
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);
|
2023-04-13 00:24:33 +02:00
|
|
|
|
|
2023-04-19 22:53:49 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2023-04-22 17:49:59 +02:00
|
|
|
|
Image ret = new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppPArgb);
|
2023-04-19 22:53:49 +02:00
|
|
|
|
Graphics g = Graphics.FromImage(ret);
|
|
|
|
|
g.Clear(Color.White);
|
|
|
|
|
|
|
|
|
|
foreach (OsmNode node in nodes)
|
|
|
|
|
{
|
|
|
|
|
foreach (OsmEdge edge in node.edges)
|
|
|
|
|
{
|
2023-04-24 19:39:04 +02:00
|
|
|
|
double priority = rm.GetPriorityForVehicle(Tag.SpeedType.car, edge, node) / 20;
|
2023-04-19 22:53:49 +02:00
|
|
|
|
Coordinates c1 = node.coordinates;
|
|
|
|
|
OsmNode nNode = rm.GetNode(edge.neighborId, edge.neighborRegion)!;
|
|
|
|
|
Coordinates c2 = nNode.coordinates;
|
|
|
|
|
|
2023-04-23 13:06:47 +02:00
|
|
|
|
Pen p = new(ColorInterp(RoadPrioStart, RoadPrioEnd, priority), PenThickness);
|
2023-04-19 22:53:49 +02:00
|
|
|
|
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<Image, Bounds>(ret, new Bounds(minLat,minLon,maxLat,maxLon));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
|
2023-04-20 19:40:50 +02:00
|
|
|
|
public static ValueTuple<Image, Bounds> DrawPath(PathResult pathResult, Image? renderOver = null, Bounds? bounds = null)
|
2023-04-19 22:53:49 +02:00
|
|
|
|
{
|
2023-04-20 19:40:50 +02:00
|
|
|
|
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);
|
2023-04-13 01:00:56 +02:00
|
|
|
|
|
2023-04-13 00:24:33 +02:00
|
|
|
|
float latDiff = maxLat - minLat;
|
|
|
|
|
float lonDiff = maxLon - minLon;
|
|
|
|
|
|
2023-04-19 22:53:49 +02:00
|
|
|
|
float scaleFactor = latDiff > lonDiff ? ImageMaxSize / latDiff : ImageMaxSize / lonDiff;
|
2023-04-13 00:24:33 +02:00
|
|
|
|
|
|
|
|
|
int pixelsX = (int)(lonDiff * scaleFactor);
|
|
|
|
|
int pixelsY = (int)(latDiff * scaleFactor);
|
2023-04-20 19:40:50 +02:00
|
|
|
|
|
2023-04-22 18:33:34 +02:00
|
|
|
|
Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppPArgb);
|
2023-04-20 19:40:50 +02:00
|
|
|
|
Graphics g = Graphics.FromImage(ret);
|
|
|
|
|
if(renderOver is null)
|
2023-04-19 22:53:49 +02:00
|
|
|
|
g.Clear(Color.White);
|
2023-04-20 19:40:50 +02:00
|
|
|
|
|
|
|
|
|
Pen p = new Pen(RouteColor, PenThickness);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < coordinates.Count - 1; i++)
|
2023-04-19 22:53:49 +02:00
|
|
|
|
{
|
2023-04-20 19:40:50 +02:00
|
|
|
|
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);
|
2023-04-19 22:53:49 +02:00
|
|
|
|
}
|
2023-04-13 00:24:33 +02:00
|
|
|
|
|
2023-04-20 19:40:50 +02:00
|
|
|
|
return new ValueTuple<Image, Bounds>(ret, new Bounds(minLat,minLon,maxLat,maxLon));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
|
2023-04-23 13:41:22 +02:00
|
|
|
|
public static ValueTuple<Image, Bounds> DrawGScores(Dictionary<OsmNode, double> gScore, Image? renderOver = null,
|
2023-04-20 19:40:50 +02:00
|
|
|
|
Bounds? bounds = null)
|
|
|
|
|
{
|
2023-04-23 13:06:29 +02:00
|
|
|
|
|
|
|
|
|
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);
|
2023-04-13 01:00:56 +02:00
|
|
|
|
|
2023-04-23 13:06:29 +02:00
|
|
|
|
double minWeight = gScore.Min(kv => kv.Value);
|
|
|
|
|
double maxWeight = gScore.Max(kv => kv.Value);
|
2023-04-20 19:40:50 +02:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2023-04-22 18:33:34 +02:00
|
|
|
|
Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppPArgb);
|
2023-04-20 19:40:50 +02:00
|
|
|
|
Graphics g = Graphics.FromImage(ret);
|
|
|
|
|
if(renderOver is null)
|
|
|
|
|
g.Clear(Color.White);
|
2023-04-22 18:34:12 +02:00
|
|
|
|
|
|
|
|
|
float pointSize = PenThickness * 1.5f;
|
2023-04-20 19:40:50 +02:00
|
|
|
|
|
2023-04-23 13:06:29 +02:00
|
|
|
|
foreach (KeyValuePair<OsmNode, double> kv in gScore)
|
2023-04-13 00:24:33 +02:00
|
|
|
|
{
|
2023-04-13 01:12:29 +02:00
|
|
|
|
double percentage = (kv.Value - minWeight) / (maxWeight - minWeight);
|
2023-04-23 13:06:47 +02:00
|
|
|
|
Brush b = new SolidBrush(ColorInterp(WeightStartColor, WeightEndColor, percentage));
|
2023-04-13 19:18:25 +02:00
|
|
|
|
|
2023-04-20 19:40:50 +02:00
|
|
|
|
float x = (kv.Key.coordinates.longitude - minLon) * scaleFactor;
|
|
|
|
|
float y = (maxLat - kv.Key.coordinates.latitude) * scaleFactor;
|
2023-04-19 22:53:49 +02:00
|
|
|
|
|
2023-04-22 18:34:12 +02:00
|
|
|
|
x -= pointSize / 2;
|
|
|
|
|
y -= pointSize / 2;
|
|
|
|
|
g.FillEllipse(b, x, y, pointSize, pointSize);
|
2023-04-13 01:00:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-20 19:40:50 +02:00
|
|
|
|
return new ValueTuple<Image, Bounds>(ret, new Bounds(minLat,minLon,maxLat,maxLon));
|
2023-04-13 01:00:56 +02:00
|
|
|
|
}
|
2023-04-20 19:40:50 +02:00
|
|
|
|
|
2023-04-13 01:00:56 +02:00
|
|
|
|
/*
|
|
|
|
|
* 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));
|
2023-04-13 00:24:33 +02:00
|
|
|
|
}
|