2023-04-13 19:18:25 +02:00
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using OSMDatastructure;
|
2023-03-14 17:00:59 +01:00
|
|
|
|
using OSMDatastructure.Graph;
|
2023-04-10 01:33:18 +02:00
|
|
|
|
using static OSMDatastructure.Tag;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
using WayType = OSMDatastructure.Tag.WayType;
|
2023-02-03 23:35:22 +01:00
|
|
|
|
|
|
|
|
|
namespace Pathfinding;
|
|
|
|
|
|
2023-04-11 01:04:19 +02:00
|
|
|
|
public static class Pathfinder
|
2023-02-03 23:35:22 +01:00
|
|
|
|
{
|
2023-04-13 19:18:25 +02:00
|
|
|
|
|
|
|
|
|
public static PathResult AStar(string workingDir, Coordinates startCoordinates, Coordinates goalCoordinates,
|
2023-04-11 01:04:19 +02:00
|
|
|
|
SpeedType vehicle, double heuristicRoadLevelPriority, double heuristicSameRoadPriority,
|
|
|
|
|
double heuristicFewJunctionsPriority)
|
2023-04-09 16:17:15 +02:00
|
|
|
|
{
|
2023-04-13 19:18:25 +02:00
|
|
|
|
DateTime startCalc = DateTime.Now;
|
2023-04-11 01:04:19 +02:00
|
|
|
|
RegionManager regionManager = new RegionManager(workingDir);
|
|
|
|
|
OsmNode? startNode = regionManager.ClosestNodeToCoordinates(startCoordinates, vehicle);
|
|
|
|
|
OsmNode? goalNode = regionManager.ClosestNodeToCoordinates(goalCoordinates, vehicle);
|
|
|
|
|
if (startNode is null || goalNode is null)
|
2023-04-13 19:18:25 +02:00
|
|
|
|
return new PathResult(DateTime.Now - startCalc, new List<PathNode>());
|
2023-04-11 01:04:19 +02:00
|
|
|
|
|
|
|
|
|
PriorityQueue<OsmNode, double> openSetfScore = new();
|
|
|
|
|
openSetfScore.Enqueue(startNode, 0);
|
|
|
|
|
Dictionary<OsmNode, OsmNode> cameFromDict = new();
|
|
|
|
|
Dictionary<OsmNode, double> gScore = new();
|
|
|
|
|
gScore.Add(startNode, 0);
|
|
|
|
|
|
|
|
|
|
while (openSetfScore.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
OsmNode currentNode = openSetfScore.Dequeue();
|
|
|
|
|
if (currentNode.Equals(goalNode))
|
2023-04-13 01:00:56 +02:00
|
|
|
|
{
|
2023-04-13 01:12:16 +02:00
|
|
|
|
Console.WriteLine("Path found.");
|
2023-04-13 19:18:25 +02:00
|
|
|
|
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;
|
2023-04-19 22:02:15 +02:00
|
|
|
|
path.AddGScores(gScore);
|
2023-04-13 19:18:25 +02:00
|
|
|
|
SaveGraph(path, outputFilepath);
|
2023-04-13 01:00:56 +02:00
|
|
|
|
return path;
|
|
|
|
|
}
|
2023-04-09 16:17:15 +02:00
|
|
|
|
|
2023-04-11 01:04:19 +02:00
|
|
|
|
foreach (OsmEdge edge in currentNode.edges)
|
|
|
|
|
{
|
|
|
|
|
OsmNode? neighbor = regionManager.GetNode(edge.neighborId, edge.neighborRegion);
|
|
|
|
|
if (neighbor is not null)
|
|
|
|
|
{
|
2023-04-13 19:18:25 +02:00
|
|
|
|
double tentativeGScore =
|
|
|
|
|
gScore[currentNode] + Weight(currentNode, neighbor, edge, vehicle, regionManager);
|
2023-04-11 01:04:19 +02:00
|
|
|
|
gScore.TryAdd(neighbor, double.MaxValue);
|
|
|
|
|
if (tentativeGScore < gScore[neighbor])
|
|
|
|
|
{
|
|
|
|
|
if (cameFromDict.ContainsKey(neighbor))
|
|
|
|
|
cameFromDict[neighbor] = currentNode;
|
|
|
|
|
else
|
|
|
|
|
cameFromDict.Add(neighbor, currentNode);
|
|
|
|
|
if (gScore.ContainsKey(neighbor))
|
|
|
|
|
gScore[neighbor] = tentativeGScore;
|
|
|
|
|
else
|
|
|
|
|
gScore.Add(neighbor, tentativeGScore);
|
|
|
|
|
double h = Heuristic(currentNode, neighbor, goalNode, edge, vehicle, regionManager,
|
|
|
|
|
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);
|
2023-04-13 19:18:25 +02:00
|
|
|
|
|
2023-04-11 01:04:19 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-13 19:18:25 +02:00
|
|
|
|
return new PathResult(DateTime.Now - startCalc, new List<PathNode>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void SaveGraph(PathResult pathResult, string outputFilepath)
|
|
|
|
|
{
|
|
|
|
|
FileStream fs = new FileStream(outputFilepath, FileMode.CreateNew);
|
|
|
|
|
JsonSerializer.Serialize(fs, pathResult, JsonSerializerOptions.Default);
|
|
|
|
|
fs.Dispose();
|
|
|
|
|
Console.WriteLine($"Saved result to {outputFilepath}");
|
2023-04-06 02:23:12 +02:00
|
|
|
|
}
|
2023-04-09 17:38:57 +02:00
|
|
|
|
|
2023-04-13 19:18:25 +02:00
|
|
|
|
private static PathResult GetPath(Dictionary<OsmNode, OsmNode> cameFromDict, OsmNode goalNode, RegionManager regionManager, TimeSpan calcFinished)
|
2023-04-09 20:41:33 +02:00
|
|
|
|
{
|
2023-04-11 01:04:19 +02:00
|
|
|
|
List<PathNode> path = new List<PathNode>();
|
|
|
|
|
OsmNode currentNode = goalNode;
|
|
|
|
|
while (cameFromDict.ContainsKey(cameFromDict[currentNode]))
|
|
|
|
|
{
|
|
|
|
|
OsmEdge? currentEdge = cameFromDict[currentNode].edges.First(edge => edge.neighborId == currentNode.nodeId);
|
|
|
|
|
HashSet<Tag>? tags =
|
|
|
|
|
regionManager.GetRegion(currentNode.coordinates)!.tagManager.GetTagsForWayId(currentEdge.wayId);
|
|
|
|
|
PathNode? newNode = PathNode.FromOsmNode(currentNode, tags);
|
|
|
|
|
if(newNode is not null)
|
|
|
|
|
path.Add(newNode);
|
|
|
|
|
currentNode = cameFromDict[currentNode];
|
|
|
|
|
}
|
2023-04-09 22:10:58 +02:00
|
|
|
|
|
2023-04-11 01:04:19 +02:00
|
|
|
|
path.Reverse();
|
2023-04-09 22:10:58 +02:00
|
|
|
|
|
2023-04-13 19:18:25 +02:00
|
|
|
|
return new PathResult(calcFinished, path);
|
2023-04-11 01:04:19 +02:00
|
|
|
|
}
|
2023-04-10 01:33:18 +02:00
|
|
|
|
|
2023-04-11 01:04:19 +02:00
|
|
|
|
private static double Weight(OsmNode fromNode, OsmNode neighborNode, OsmEdge edge, SpeedType vehicle, RegionManager regionManager)
|
|
|
|
|
{
|
|
|
|
|
double distance = Utils.DistanceBetween(fromNode, neighborNode);
|
|
|
|
|
double speed = regionManager.GetSpeedForEdge(fromNode, edge.wayId, vehicle);
|
|
|
|
|
double prio = GetPriorityVehicleRoad(edge, vehicle, regionManager.GetRegion(fromNode.coordinates)!);
|
2023-04-13 01:12:16 +02:00
|
|
|
|
return distance / (speed + prio * 50);
|
2023-04-11 01:04:19 +02:00
|
|
|
|
}
|
2023-04-10 01:33:18 +02:00
|
|
|
|
|
2023-04-11 01:04:19 +02:00
|
|
|
|
private static double Heuristic(OsmNode fromNode, OsmNode neighborNode, OsmNode goalNode, OsmEdge edge, SpeedType vehicle, RegionManager regionManager, double roadPriorityFactor, double junctionFactor, double sameRoadFactor)
|
|
|
|
|
{
|
|
|
|
|
double roadPriority = GetPriorityVehicleRoad(edge, vehicle, regionManager.GetRegion(fromNode.coordinates)!) * roadPriorityFactor;
|
|
|
|
|
|
|
|
|
|
TagManager curTags = regionManager.GetRegion(fromNode.coordinates)!.tagManager;
|
|
|
|
|
TagManager nextTags = regionManager.GetRegion(neighborNode.coordinates)!.tagManager;
|
|
|
|
|
|
|
|
|
|
bool sameName = false;
|
|
|
|
|
string? curName = (string?)curTags.GetTag(edge.wayId, TagType.name);
|
|
|
|
|
bool sameRef = false;
|
|
|
|
|
string? curRef = (string?)curTags.GetTag(edge.wayId, TagType.tagref);
|
|
|
|
|
if(curName is not null)
|
|
|
|
|
foreach (OsmEdge pEdge in neighborNode.edges)
|
|
|
|
|
{
|
|
|
|
|
if ((string?)nextTags.GetTag(pEdge.wayId, TagType.name) == curName)
|
|
|
|
|
sameName = true;
|
|
|
|
|
if ((string?)nextTags.GetTag(pEdge.wayId, TagType.tagref) == curRef)
|
|
|
|
|
sameRef = true;
|
2023-04-10 01:33:18 +02:00
|
|
|
|
}
|
2023-04-11 01:04:19 +02:00
|
|
|
|
double sameRoadName = (sameRef || sameName ? 1 : 0) * sameRoadFactor;
|
2023-04-10 01:33:18 +02:00
|
|
|
|
|
2023-04-11 01:04:19 +02:00
|
|
|
|
double junctionCount = (neighborNode.edges.Count > 2 ? 0 : 1) * junctionFactor;
|
|
|
|
|
|
|
|
|
|
//Console.WriteLine($"{roadPriority:000.00} {sameRoadName:000.00} {junctionCount:000.00} {distanceImprovement:+000.00;-000.00;0000.00}");
|
2023-04-09 20:41:33 +02:00
|
|
|
|
|
2023-04-13 01:12:16 +02:00
|
|
|
|
return Utils.DistanceBetween(neighborNode, goalNode) - (roadPriority + sameRoadName + junctionCount) * 20;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
}
|
2023-04-11 01:04:19 +02:00
|
|
|
|
|
2023-04-10 01:33:18 +02:00
|
|
|
|
private static double GetPriorityVehicleRoad(OsmEdge edge, SpeedType vehicle, Region region)
|
2023-04-09 20:41:33 +02:00
|
|
|
|
{
|
2023-04-10 01:33:18 +02:00
|
|
|
|
if (vehicle == SpeedType.any)
|
2023-04-09 20:41:33 +02:00
|
|
|
|
return 1;
|
2023-04-10 01:33:18 +02:00
|
|
|
|
WayType? wayType = (WayType?)region.tagManager.GetTag(edge.wayId, TagType.highway);
|
2023-04-09 20:41:33 +02:00
|
|
|
|
if(wayType is null)
|
2023-04-11 01:04:19 +02:00
|
|
|
|
return 0;
|
2023-04-10 01:33:18 +02:00
|
|
|
|
if (vehicle == SpeedType.car)
|
2023-04-09 20:41:33 +02:00
|
|
|
|
{
|
|
|
|
|
switch (wayType)
|
|
|
|
|
{
|
|
|
|
|
case WayType.motorway:
|
|
|
|
|
case WayType.motorway_link:
|
|
|
|
|
case WayType.motorroad:
|
|
|
|
|
case WayType.trunk:
|
|
|
|
|
case WayType.trunk_link:
|
|
|
|
|
case WayType.primary:
|
|
|
|
|
case WayType.primary_link:
|
2023-04-10 01:33:18 +02:00
|
|
|
|
return 8;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
case WayType.secondary:
|
|
|
|
|
case WayType.secondary_link:
|
2023-04-11 01:04:19 +02:00
|
|
|
|
return 6;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
case WayType.tertiary:
|
|
|
|
|
case WayType.tertiary_link:
|
2023-04-10 01:33:18 +02:00
|
|
|
|
return 5;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
case WayType.unclassified:
|
|
|
|
|
case WayType.residential:
|
|
|
|
|
case WayType.road:
|
|
|
|
|
case WayType.living_street:
|
2023-04-11 01:04:19 +02:00
|
|
|
|
return 2;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
case WayType.service:
|
|
|
|
|
case WayType.track:
|
2023-04-10 01:33:18 +02:00
|
|
|
|
return 0.01;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-10 01:33:18 +02:00
|
|
|
|
if (vehicle == SpeedType.pedestrian)
|
2023-04-09 20:41:33 +02:00
|
|
|
|
{
|
|
|
|
|
switch (wayType)
|
|
|
|
|
{
|
|
|
|
|
case WayType.pedestrian:
|
|
|
|
|
case WayType.corridor:
|
|
|
|
|
case WayType.footway:
|
|
|
|
|
case WayType.path:
|
|
|
|
|
case WayType.steps:
|
|
|
|
|
case WayType.residential:
|
|
|
|
|
case WayType.living_street:
|
2023-04-09 22:10:58 +02:00
|
|
|
|
return 10;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
case WayType.service:
|
|
|
|
|
case WayType.cycleway:
|
|
|
|
|
case WayType.bridleway:
|
|
|
|
|
case WayType.road:
|
|
|
|
|
case WayType.track:
|
|
|
|
|
case WayType.unclassified:
|
2023-04-09 22:10:58 +02:00
|
|
|
|
return 5;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
case WayType.tertiary:
|
|
|
|
|
case WayType.tertiary_link:
|
|
|
|
|
case WayType.escape:
|
2023-04-09 22:10:58 +02:00
|
|
|
|
return 2;
|
2023-04-09 20:41:33 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-10 01:33:18 +02:00
|
|
|
|
return 0.01;
|
2023-04-09 17:38:57 +02:00
|
|
|
|
}
|
2023-02-03 23:35:22 +01:00
|
|
|
|
}
|