Compare commits

...

4 Commits

Author SHA1 Message Date
b87d8a0300 Changed calls to new backend.
Pathfinder now finds the result and returns itself.
Pathfinder includes PathResult for later usage.
2023-04-20 19:41:40 +02:00
23429c8a00 Renderer now renders 3 different layers over each other: Area (Regions), gScore-Weights (as blobs) and PathResult path.
Returns the Image instead of saving to disk.
2023-04-20 19:40:50 +02:00
946fa0206b Made Pathfinder non-static to enable field-usage in other methods/classes.
Added fields RegionManager, workingDirectory, PathResult (result of AStar), gScore (result of AStar). Fields later used in Renderer.

SaveGraph is now called SaveResult and serializes PathResult as JSON.
2023-04-20 19:39:18 +02:00
5a1dce9883 PathResult now only includes Path and calcTime.
Other fields have been moved to Pathfinder
2023-04-20 19:37:34 +02:00
5 changed files with 128 additions and 125 deletions

View File

@ -16,20 +16,19 @@ var app = builder.Build();
app.MapGet("/getRoute", (float latStart, float lonStart, float latEnd, float lonEnd, Tag.SpeedType vehicle, double stayOnSameRoadPriority, double useHigherLevelRoadsPriority, double useRoadsWithLessJunctionsPriority) =>
{
PathResult result = Pathfinder.AStar("D:/stuttgart-regbez-latest", new Coordinates(latStart, lonStart),
Pathfinder result = new Pathfinder("D:/stuttgart-regbez-latest").AStar(new Coordinates(latStart, lonStart),
new Coordinates(latEnd, lonEnd), vehicle, useHigherLevelRoadsPriority, stayOnSameRoadPriority,
useRoadsWithLessJunctionsPriority);
return result;
return result.pathResult;
}
);
app.MapGet("/getShortestRoute", (float latStart, float lonStart, float latEnd, float lonEnd) =>
{
PathResult result = Pathfinder.AStar("D:/stuttgart-regbez-latest", new Coordinates(latStart, lonStart),
Pathfinder result = new Pathfinder("D:/stuttgart-regbez-latest").AStar(new Coordinates(latStart, lonStart),
new Coordinates(latEnd, lonEnd), Tag.SpeedType.any, 0, 0,
0);
return result;
return result.pathResult;
}
);

View File

@ -1,5 +1,4 @@
using System.Text.Json.Serialization;
using OSMDatastructure.Graph;
namespace Pathfinding;
@ -7,41 +6,11 @@ public class PathResult
{
[JsonInclude]public TimeSpan calcTime;
[JsonInclude]public List<PathNode> pathNodes;
[JsonInclude]public Dictionary<ulong, double>? gScore;
[JsonInclude]public HashSet<OsmNode>? gScoreNodes;
[JsonIgnore] public RegionManager? regionManager { get; set; }
public string? name { get; set; }
[JsonConstructor]
public PathResult(TimeSpan calcTime, List<PathNode> pathNodes, Dictionary<ulong, double>? gScore, HashSet<OsmNode>? gScoreNodes)
{
this.calcTime = calcTime;
this.pathNodes = pathNodes;
this.gScore = gScore;
this.gScoreNodes = gScoreNodes;
}
public PathResult(TimeSpan calcTime, List<PathNode> pathNodes)
{
this.calcTime = calcTime;
this.pathNodes = pathNodes;
}
public PathResult(TimeSpan calcTime, List<PathNode> pathNodes, Dictionary<OsmNode, double> gScore)
{
this.calcTime = calcTime;
this.pathNodes = pathNodes;
AddGScores(gScore);
}
public void AddGScores(Dictionary<OsmNode, double> gScore)
{
this.gScore = new();
this.gScoreNodes = new();
foreach (KeyValuePair<OsmNode, double> kv in gScore)
{
this.gScore.Add(kv.Key.nodeId, kv.Value);
this.gScoreNodes.Add(kv.Key);
}
}
}

View File

@ -6,25 +6,41 @@ using WayType = OSMDatastructure.Tag.WayType;
namespace Pathfinding;
public static class Pathfinder
//TODO check parameters for all functions and determine global fields
public class Pathfinder
{
public static PathResult AStar(string workingDir, Coordinates startCoordinates, Coordinates goalCoordinates,
public RegionManager regionManager;
public readonly string workingDir;
public PathResult? pathResult;
public Dictionary<OsmNode, double>? gScore;
public Pathfinder(string workingDirectory)
{
if (!Path.Exists(workingDirectory))
throw new DirectoryNotFoundException(workingDirectory);
regionManager = new(workingDirectory);
workingDir = workingDirectory;
}
public Pathfinder AStar(Coordinates startCoordinates, Coordinates goalCoordinates,
SpeedType vehicle, double heuristicRoadLevelPriority, double heuristicSameRoadPriority,
double heuristicFewJunctionsPriority)
{
DateTime startCalc = DateTime.Now;
RegionManager regionManager = new RegionManager(workingDir);
regionManager = new RegionManager(workingDir);
OsmNode? startNode = regionManager.ClosestNodeToCoordinates(startCoordinates, vehicle);
OsmNode? goalNode = regionManager.ClosestNodeToCoordinates(goalCoordinates, vehicle);
if (startNode is null || goalNode is null)
return new PathResult(DateTime.Now - startCalc, new List<PathNode>());
{
pathResult = new(DateTime.Now - startCalc, new List<PathNode>());
return this;
}
PriorityQueue<OsmNode, double> openSetfScore = new();
openSetfScore.Enqueue(startNode, 0);
Dictionary<OsmNode, OsmNode> cameFromDict = new();
Dictionary<OsmNode, double> gScore = new();
gScore.Add(startNode, 0);
gScore = new() { { startNode, 0 } };
while (openSetfScore.Count > 0)
{
@ -32,14 +48,8 @@ public static class Pathfinder
if (currentNode.Equals(goalNode))
{
Console.WriteLine("Path found.");
PathResult path = GetPath(cameFromDict, goalNode, regionManager, DateTime.Now - startCalc);
string fileName = $"{new DirectoryInfo(workingDir).Name}-{DateTime.Now.ToFileTime()}.result";
string outputFilepath = Path.Join(Directory.GetParent(workingDir)!.FullName, fileName);
path.name = outputFilepath;
path.AddGScores(gScore);
SaveGraph(path, outputFilepath);
path.regionManager = regionManager;
return path;
this.pathResult = GetPath(cameFromDict, goalNode, DateTime.Now - startCalc);
return this;
}
foreach (OsmEdge edge in currentNode.edges)
@ -48,7 +58,7 @@ public static class Pathfinder
if (neighbor is not null)
{
double tentativeGScore =
gScore[currentNode] + Weight(currentNode, neighbor, edge, vehicle, regionManager);
gScore[currentNode] + Weight(currentNode, neighbor, edge, vehicle);
gScore.TryAdd(neighbor, double.MaxValue);
if (tentativeGScore < gScore[neighbor])
{
@ -60,7 +70,7 @@ public static class Pathfinder
gScore[neighbor] = tentativeGScore;
else
gScore.Add(neighbor, tentativeGScore);
double h = Heuristic(currentNode, neighbor, goalNode, edge, vehicle, regionManager,
double h = Heuristic(currentNode, neighbor, goalNode, edge, vehicle,
heuristicRoadLevelPriority, heuristicFewJunctionsPriority, heuristicSameRoadPriority);
//Console.WriteLine($"Queue: {openSetfScore.Count:00000} Current Distance: {Utils.DistanceBetween(currentNode, goalNode):000000.00} Visited: {cameFromDict.Count:00000} Current heuristic: {h:00000.00}");
openSetfScore.Enqueue(neighbor, tentativeGScore + h);
@ -69,20 +79,21 @@ public static class Pathfinder
}
}
return new PathResult(DateTime.Now - startCalc, new List<PathNode>());
pathResult = new(DateTime.Now - startCalc, new List<PathNode>());
return this;
}
private static void SaveGraph(PathResult pathResult, string outputFilepath)
public void SaveResult(string path)
{
FileStream fs = new FileStream(outputFilepath, FileMode.CreateNew);
FileStream fs = new (path, FileMode.CreateNew);
JsonSerializer.Serialize(fs, pathResult, JsonSerializerOptions.Default);
fs.Dispose();
Console.WriteLine($"Saved result to {outputFilepath}");
Console.WriteLine($"Saved result to {path}");
}
private static PathResult GetPath(Dictionary<OsmNode, OsmNode> cameFromDict, OsmNode goalNode, RegionManager regionManager, TimeSpan calcFinished)
private PathResult GetPath(Dictionary<OsmNode, OsmNode> cameFromDict, OsmNode goalNode, TimeSpan calcFinished)
{
List<PathNode> path = new List<PathNode>();
List<PathNode> path = new();
OsmNode currentNode = goalNode;
while (cameFromDict.ContainsKey(cameFromDict[currentNode]))
{
@ -100,15 +111,15 @@ public static class Pathfinder
return new PathResult(calcFinished, path);
}
private static double Weight(OsmNode fromNode, OsmNode neighborNode, OsmEdge edge, SpeedType vehicle, RegionManager regionManager)
private double Weight(OsmNode fromNode, OsmNode neighborNode, OsmEdge edge, SpeedType vehicle)
{
double distance = Utils.DistanceBetween(fromNode, neighborNode);
double speed = regionManager.GetSpeedForEdge(fromNode, edge.wayId, vehicle);
double prio = GetPriorityVehicleRoad(edge, vehicle, regionManager.GetRegion(fromNode.coordinates)!);
//double prio = GetPriorityVehicleRoad(edge, vehicle, regionManager.GetRegion(fromNode.coordinates)!);
return distance / speed;
}
private static double Heuristic(OsmNode fromNode, OsmNode neighborNode, OsmNode goalNode, OsmEdge edge, SpeedType vehicle, RegionManager regionManager, double roadPriorityFactor, double junctionFactor, double sameRoadFactor)
private double Heuristic(OsmNode fromNode, OsmNode neighborNode, OsmNode goalNode, OsmEdge edge, SpeedType vehicle, double roadPriorityFactor, double junctionFactor, double sameRoadFactor)
{
double roadPriority = GetPriorityVehicleRoad(edge, vehicle, regionManager.GetRegion(fromNode.coordinates)!) * roadPriorityFactor;

View File

@ -11,6 +11,10 @@ public static class Renderer
{
private const int ImageMaxSize = 20000;
private const float PenThickness = 4;
private static readonly Color RouteColor = Color.Red;
private static readonly Color WeightStartColor = Color.FromArgb(0, 0, 255);
private static readonly Color WeightCenterColor = Color.FromArgb(255, 255, 0);
private static readonly Color WeightEndColor = Color.FromArgb(0, 255, 0);
public class Bounds
{
@ -26,17 +30,16 @@ public static class Renderer
}
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
public static void DrawGraph(string resultPath, Image? area = null, Bounds? bounds = null)
public static Image DrawPathfinder(Pathfinder pathfinder)
{
FileStream fs = new FileStream(resultPath, FileMode.Open);
PathResult graph = JsonSerializer.Deserialize<PathResult>(fs)!;
List<Coordinates> coords = new List<Coordinates>();
foreach (PathNode node in graph.pathNodes)
coords.Add(node.coordinates);
string workingDir = new DirectoryInfo(resultPath).FullName;
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);
Image renderedImage = DrawLoadedNodes(graph.gScoreNodes!, graph.gScore!, coords, area, bounds);
renderedImage.Save($"{workingDir}-routing.png");
return areaGScorePathRender.Item1;
}
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
@ -63,6 +66,7 @@ public static class Renderer
Graphics g = Graphics.FromImage(ret);
g.Clear(Color.White);
//TODO Use road priority for roadcolor
Color start = Color.FromArgb(255, 25, 25, 25);
Color center = Color.FromArgb(255, 0, 0, 0);
Color end = Color.FromArgb(255, 0, 255, 0);
@ -89,13 +93,54 @@ public static class Renderer
}
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
public static Image DrawLoadedNodes(HashSet<OsmNode> nodes, Dictionary<ulong, double> gScoreDict,
List<Coordinates> pathCoordinates, Image? renderOver = null, Bounds? bounds = null)
public static ValueTuple<Image, Bounds> DrawPath(PathResult pathResult, Image? renderOver = null, Bounds? bounds = null)
{
float minLat = bounds?.minLat ?? nodes.Min(node => node.coordinates.latitude);
float minLon = bounds?.minLon ?? nodes.Min(node => node.coordinates.longitude);
float maxLat = bounds?.maxLat ?? nodes.Max(node => node.coordinates.latitude);
float maxLon = bounds?.maxLon ?? nodes.Max(node => node.coordinates.longitude);
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 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);
Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppRgb);
Graphics g = Graphics.FromImage(ret);
if(renderOver is null)
g.Clear(Color.White);
Pen p = new Pen(RouteColor, PenThickness);
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);
}
return new ValueTuple<Image, Bounds>(ret, new Bounds(minLat,minLon,maxLat,maxLon));
}
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
public static ValueTuple<Image, Bounds> DrawGScores(Dictionary<OsmNode, double> gScoreDict, Image? renderOver = null,
Bounds? bounds = null)
{
float minLat = bounds?.minLat ?? gScoreDict.Min(kv => kv.Key.coordinates.latitude);
float minLon = bounds?.minLon ?? gScoreDict.Min(kv => kv.Key.coordinates.longitude);
float maxLat = bounds?.maxLat ?? gScoreDict.Max(kv => kv.Key.coordinates.latitude);
float maxLon = bounds?.maxLon ?? gScoreDict.Max(kv => kv.Key.coordinates.longitude);
double minWeight = gScoreDict.Min(kv => kv.Value);
double maxWeight = gScoreDict.Max(kv => kv.Value);
@ -108,52 +153,25 @@ public static class Renderer
int pixelsX = (int)(lonDiff * scaleFactor);
int pixelsY = (int)(latDiff * scaleFactor);
Image ret;
Graphics g;
if (renderOver is null)
{
ret = new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppRgb);
g = Graphics.FromImage(ret);
Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppRgb);
Graphics g = Graphics.FromImage(ret);
if(renderOver is null)
g.Clear(Color.White);
}
else
{
ret = renderOver;
g = Graphics.FromImage(ret);
}
Color start = Color.FromArgb(0, 0, 255);
Color center = Color.FromArgb(255, 255, 0);
Color end = Color.FromArgb(0, 255, 0);
foreach (KeyValuePair<ulong, double> kv in gScoreDict)
foreach (KeyValuePair<OsmNode, double> kv in gScoreDict)
{
double percentage = (kv.Value - minWeight) / (maxWeight - minWeight);
Brush b = new SolidBrush(GradientPick(percentage, start, center, end));
OsmNode node = nodes.First(node => node.nodeId.Equals(kv.Key));
Brush b = new SolidBrush(GradientPick(percentage, WeightStartColor, WeightCenterColor, WeightEndColor));
float x = (node.coordinates.longitude - minLon) * scaleFactor;
float y = (maxLat - node.coordinates.latitude) * scaleFactor;
float x = (kv.Key.coordinates.longitude - minLon) * scaleFactor;
float y = (maxLat - kv.Key.coordinates.latitude) * scaleFactor;
x -= (PenThickness * 1.5f) / 2;
y -= (PenThickness * 1.5f) / 2;
g.FillEllipse(b, x, y, PenThickness * 1.5f, PenThickness * 1.5f);
}
Pen p = new Pen(Color.Red, PenThickness);
for (int i = 0; i < pathCoordinates.Count - 1; i++)
{
Coordinates c1 = pathCoordinates[i];
Coordinates c2 = pathCoordinates[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);
}
return ret;
return new ValueTuple<Image, Bounds>(ret, new Bounds(minLat,minLon,maxLat,maxLon));
}
/*

View File

@ -1,4 +1,5 @@
using System.Drawing;
using System.Drawing.Imaging;
using OSMDatastructure;
using OSMDatastructure.Graph;
using Pathfinding;
@ -11,25 +12,30 @@ public class Server
public static void Main(string[] args)
{
ConsoleWriter newConsole = new ConsoleWriter();
ConsoleWriter newConsole = new();
Console.SetOut(newConsole);
Console.SetError(newConsole);
string workingDir = "D:/stuttgart-regbez-latest";
//RegionConverter.ConvertXMLToRegions("D:/stuttgart-regbez-latest.osm", "D:/stuttgart-regbez-latest");
//RegionConverter.ConvertXMLToRegions("D:/map.osm", "D:/map");
//RegionConverter.ConvertXMLToRegions("D:/germany-latest.osm", "D:/germany-latest");
Coordinates start = new Coordinates(48.7933798f, 9.8275859f);
Coordinates finish = new Coordinates(48.795918f, 9.021618f);
PathResult result = Pathfinder.AStar("D:/stuttgart-regbez-latest", start,
Coordinates start = new (48.7933798f, 9.8275859f);
Coordinates finish = new (48.795918f, 9.021618f);
Pathfinder result = new Pathfinder(workingDir).AStar(start,
finish, Tag.SpeedType.car, 0.01, 0.0001,
0);
Console.WriteLine("Drawing area");
ValueTuple<Image, Renderer.Bounds> area = Renderer.DrawArea(result.regionManager);
string parentFolder = new DirectoryInfo(workingDir).Parent!.FullName;
string resultFileName = $"{new DirectoryInfo(workingDir).Name}-{DateTime.Now.ToFileTime()}.result";
result.SaveResult(Path.Join(parentFolder, resultFileName));
Console.WriteLine("Drawing route");
Renderer.DrawGraph(result.name, area.Item1, area.Item2);
string renderFileName = $"{new DirectoryInfo(workingDir).Name}-{DateTime.Now.ToFileTime()}.render.png";
Image render = Renderer.DrawPathfinder(result);
#pragma warning disable CA1416
render.Save(Path.Join(parentFolder, renderFileName), ImageFormat.Png);
#pragma warning restore CA1416
}
}