2023-04-21 13:43:50 +02:00
|
|
|
|
using System.Text.Json;
|
2023-04-13 19:18:25 +02:00
|
|
|
|
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-02-03 23:35:22 +01:00
|
|
|
|
|
|
|
|
|
namespace Pathfinding;
|
|
|
|
|
|
2023-04-20 19:39:18 +02:00
|
|
|
|
public class Pathfinder
|
2023-02-03 23:35:22 +01:00
|
|
|
|
{
|
2023-04-13 19:18:25 +02:00
|
|
|
|
|
2023-04-20 19:39:18 +02:00
|
|
|
|
public RegionManager regionManager;
|
|
|
|
|
public PathResult? pathResult;
|
|
|
|
|
public Dictionary<OsmNode, double>? gScore;
|
2023-04-20 22:58:50 +02:00
|
|
|
|
private Dictionary<OsmNode, OsmNode>? _cameFromDict;
|
2023-04-20 22:58:27 +02:00
|
|
|
|
private SpeedType _speedType;
|
2023-04-20 19:39:18 +02:00
|
|
|
|
|
|
|
|
|
public Pathfinder(string workingDirectory)
|
|
|
|
|
{
|
|
|
|
|
if (!Path.Exists(workingDirectory))
|
|
|
|
|
throw new DirectoryNotFoundException(workingDirectory);
|
|
|
|
|
regionManager = new(workingDirectory);
|
2023-04-21 15:13:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Pathfinder(RegionManager regionManager)
|
|
|
|
|
{
|
|
|
|
|
this.regionManager = regionManager;
|
2023-04-20 19:39:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Pathfinder AStar(Coordinates startCoordinates, Coordinates goalCoordinates,
|
2023-04-11 01:04:19 +02:00
|
|
|
|
SpeedType vehicle, double heuristicRoadLevelPriority, double heuristicSameRoadPriority,
|
2023-04-21 00:42:21 +02:00
|
|
|
|
double heuristicFewJunctionsPriority, double angleWeightFactor)
|
2023-04-09 16:17:15 +02:00
|
|
|
|
{
|
2023-04-13 19:18:25 +02:00
|
|
|
|
DateTime startCalc = DateTime.Now;
|
2023-04-20 22:58:27 +02:00
|
|
|
|
_speedType = vehicle;
|
|
|
|
|
OsmNode? startNode = regionManager.ClosestNodeToCoordinates(startCoordinates, _speedType);
|
|
|
|
|
OsmNode? goalNode = regionManager.ClosestNodeToCoordinates(goalCoordinates, _speedType);
|
2023-04-11 01:04:19 +02:00
|
|
|
|
if (startNode is null || goalNode is null)
|
2023-04-20 19:39:18 +02:00
|
|
|
|
{
|
2023-04-21 11:40:26 +02:00
|
|
|
|
pathResult = new(DateTime.Now - startCalc, new List<PathNode>(),0 ,0);
|
2023-04-20 19:39:18 +02:00
|
|
|
|
return this;
|
|
|
|
|
}
|
2023-04-11 01:04:19 +02:00
|
|
|
|
|
|
|
|
|
PriorityQueue<OsmNode, double> openSetfScore = new();
|
|
|
|
|
openSetfScore.Enqueue(startNode, 0);
|
2023-04-20 19:39:18 +02:00
|
|
|
|
gScore = new() { { startNode, 0 } };
|
2023-04-20 22:58:50 +02:00
|
|
|
|
_cameFromDict = new();
|
2023-04-11 01:04:19 +02:00
|
|
|
|
|
|
|
|
|
while (openSetfScore.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
OsmNode currentNode = openSetfScore.Dequeue();
|
|
|
|
|
if (currentNode.Equals(goalNode))
|
2023-04-13 01:00:56 +02:00
|
|
|
|
{
|
2023-04-21 00:42:21 +02:00
|
|
|
|
TimeSpan calcTime = DateTime.Now - startCalc;
|
|
|
|
|
pathResult = GetPath(goalNode, calcTime);
|
2023-04-21 11:40:26 +02:00
|
|
|
|
Console.WriteLine($"Path found. {calcTime} PathLength {pathResult.pathNodes.Count} VisitedNodes {gScore.Count} Distance {pathResult.distance} Duration {pathResult.weight}");
|
2023-04-20 19:39:18 +02:00
|
|
|
|
return this;
|
2023-04-13 01:00:56 +02:00
|
|
|
|
}
|
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 =
|
2023-04-21 14:28:02 +02:00
|
|
|
|
gScore[currentNode] + Weight(currentNode, neighbor, edge);
|
2023-04-11 01:04:19 +02:00
|
|
|
|
gScore.TryAdd(neighbor, double.MaxValue);
|
|
|
|
|
if (tentativeGScore < gScore[neighbor])
|
|
|
|
|
{
|
2023-04-20 22:58:50 +02:00
|
|
|
|
if(!_cameFromDict.TryAdd(neighbor, currentNode))
|
|
|
|
|
_cameFromDict[neighbor] = currentNode;
|
2023-04-20 22:53:23 +02:00
|
|
|
|
gScore[neighbor] = tentativeGScore;
|
2023-04-20 22:58:27 +02:00
|
|
|
|
double h = Heuristic(currentNode, neighbor, goalNode, edge,
|
2023-04-21 11:01:31 +02:00
|
|
|
|
heuristicRoadLevelPriority, heuristicFewJunctionsPriority, heuristicSameRoadPriority, angleWeightFactor);
|
2023-04-11 01:04:19 +02:00
|
|
|
|
//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-21 11:40:26 +02:00
|
|
|
|
pathResult = new(DateTime.Now - startCalc, new List<PathNode>(),0 ,0);
|
2023-04-20 19:39:18 +02:00
|
|
|
|
return this;
|
2023-04-13 19:18:25 +02:00
|
|
|
|
}
|
2023-04-21 13:41:01 +02:00
|
|
|
|
|
2023-04-21 14:28:02 +02:00
|
|
|
|
private double Weight(OsmNode currentNode, OsmNode neighborNode, OsmEdge edge)
|
2023-04-11 01:04:19 +02:00
|
|
|
|
{
|
2023-04-21 00:42:21 +02:00
|
|
|
|
double distance = Utils.DistanceBetween(currentNode, neighborNode);
|
|
|
|
|
double speed = regionManager.GetSpeedForEdge(currentNode, edge.wayId, _speedType);
|
|
|
|
|
|
|
|
|
|
double angle = 1;
|
|
|
|
|
if (_cameFromDict!.ContainsKey(currentNode))
|
|
|
|
|
{
|
|
|
|
|
OsmNode previousNode = _cameFromDict[currentNode];
|
2023-04-21 14:28:02 +02:00
|
|
|
|
Vector v1 = new(currentNode, previousNode);
|
2023-04-21 11:01:31 +02:00
|
|
|
|
Vector v2 = new(currentNode, neighborNode);
|
2023-04-21 00:42:21 +02:00
|
|
|
|
double nodeAngle = v1.Angle(v2);
|
2023-04-21 14:28:02 +02:00
|
|
|
|
angle = (nodeAngle / 180);
|
2023-04-21 00:42:21 +02:00
|
|
|
|
}
|
2023-04-21 13:43:50 +02:00
|
|
|
|
double prio = regionManager.GetPriorityForVehicle(_speedType,edge, currentNode);
|
2023-04-21 14:28:02 +02:00
|
|
|
|
return distance / (1 + speed * angle);
|
2023-04-11 01:04:19 +02:00
|
|
|
|
}
|
2023-04-10 01:33:18 +02:00
|
|
|
|
|
2023-04-21 11:01:31 +02:00
|
|
|
|
private double Heuristic(OsmNode currentNode, OsmNode neighborNode, OsmNode goalNode, OsmEdge edge, double roadPriorityFactor, double junctionFactor, double sameRoadFactor, double nodeAngleFactor)
|
2023-04-11 01:04:19 +02:00
|
|
|
|
{
|
2023-04-21 13:43:50 +02:00
|
|
|
|
double roadPriority = regionManager.GetPriorityForVehicle(_speedType, edge, currentNode) * roadPriorityFactor;
|
2023-04-21 11:01:31 +02:00
|
|
|
|
if (roadPriority == 0)
|
|
|
|
|
return double.MaxValue;
|
|
|
|
|
|
|
|
|
|
double speed = regionManager.GetSpeedForEdge(currentNode, edge.wayId, _speedType) * 0.0003;
|
2023-04-11 01:04:19 +02:00
|
|
|
|
|
2023-04-21 00:42:21 +02:00
|
|
|
|
TagManager curTags = regionManager.GetRegion(currentNode.coordinates)!.tagManager;
|
2023-04-11 01:04:19 +02:00
|
|
|
|
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;
|
2023-04-21 11:01:31 +02:00
|
|
|
|
|
|
|
|
|
double angle = 0;
|
|
|
|
|
if (_cameFromDict!.ContainsKey(currentNode))
|
|
|
|
|
{
|
|
|
|
|
OsmNode previousNode = _cameFromDict[currentNode];
|
|
|
|
|
Vector v1 = new(previousNode, currentNode);
|
|
|
|
|
Vector v2 = new(currentNode, neighborNode);
|
|
|
|
|
double nodeAngle = v1.Angle(v2);
|
|
|
|
|
angle = ((180 - nodeAngle) / 180) * nodeAngleFactor;
|
|
|
|
|
}
|
|
|
|
|
return Utils.DistanceBetween(neighborNode, goalNode) / (roadPriority + sameRoadName + junctionCount + angle);
|
2023-04-09 20:41:33 +02:00
|
|
|
|
}
|
2023-04-21 00:42:21 +02:00
|
|
|
|
|
2023-04-21 13:41:01 +02:00
|
|
|
|
public void SaveResult(string path)
|
|
|
|
|
{
|
|
|
|
|
FileStream fs = new (path, FileMode.CreateNew);
|
|
|
|
|
JsonSerializer.Serialize(fs, pathResult, JsonSerializerOptions.Default);
|
|
|
|
|
fs.Dispose();
|
|
|
|
|
Console.WriteLine($"Saved result to {path}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private PathResult GetPath(OsmNode goalNode, TimeSpan calcFinished)
|
|
|
|
|
{
|
|
|
|
|
List<PathNode> path = new();
|
|
|
|
|
OsmNode currentNode = goalNode;
|
|
|
|
|
double retDistance = 0;
|
|
|
|
|
double weight = 0;
|
|
|
|
|
while (_cameFromDict!.ContainsKey(_cameFromDict[currentNode]))
|
|
|
|
|
{
|
2023-04-21 13:43:50 +02:00
|
|
|
|
OsmEdge? currentEdge = _cameFromDict[currentNode].edges.FirstOrDefault(edge => edge.neighborId == currentNode.nodeId);
|
2023-04-21 13:41:01 +02:00
|
|
|
|
HashSet<Tag>? tags =
|
2023-04-21 13:43:50 +02:00
|
|
|
|
regionManager.GetRegion(currentNode.coordinates)!.tagManager.GetTagsForWayId(currentEdge!.wayId);
|
2023-04-21 13:41:01 +02:00
|
|
|
|
PathNode? newNode = PathNode.FromOsmNode(currentNode, tags);
|
|
|
|
|
if(newNode is not null)
|
|
|
|
|
path.Add(newNode);
|
|
|
|
|
|
|
|
|
|
double distance = Utils.DistanceBetween(currentNode, _cameFromDict[currentNode]);
|
|
|
|
|
retDistance += distance;
|
|
|
|
|
weight += regionManager.GetSpeedForEdge(_cameFromDict[currentNode], currentEdge.wayId, _speedType);
|
|
|
|
|
|
|
|
|
|
currentNode = _cameFromDict[currentNode];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
path.Reverse();
|
|
|
|
|
|
|
|
|
|
return new PathResult(calcFinished, path, retDistance, retDistance / (weight / path.Count));
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-21 00:42:21 +02:00
|
|
|
|
private class Vector
|
|
|
|
|
{
|
2023-04-21 13:43:50 +02:00
|
|
|
|
public readonly float x, y;
|
2023-04-21 00:42:21 +02:00
|
|
|
|
|
|
|
|
|
public Vector(float x, float y)
|
|
|
|
|
{
|
|
|
|
|
this.x = x;
|
|
|
|
|
this.y = y;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-21 11:01:31 +02:00
|
|
|
|
public Vector(OsmNode n1, OsmNode n2)
|
|
|
|
|
{
|
|
|
|
|
this.x = n1.coordinates.longitude - n2.coordinates.longitude;
|
|
|
|
|
this.y = n1.coordinates.latitude - n2.coordinates.latitude;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-21 00:42:21 +02:00
|
|
|
|
public double Angle(Vector v2)
|
|
|
|
|
{
|
|
|
|
|
return Angle(this, v2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static double Angle(Vector v1, Vector v2)
|
|
|
|
|
{
|
|
|
|
|
double dotProd = v1.x * v2.x + v1.y * v2.y;
|
|
|
|
|
double v1L = Math.Sqrt(v1.x * v1.x + v1.y * v1.y);
|
|
|
|
|
double v2L = Math.Sqrt(v2.x * v2.x + v2.y * v2.y);
|
2023-04-21 11:01:31 +02:00
|
|
|
|
double ang = Math.Acos(dotProd / (v1L * v2L));
|
|
|
|
|
double angle = Utils.RadiansToDegrees(ang);
|
|
|
|
|
return angle;
|
2023-04-21 00:42:21 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-02-03 23:35:22 +01:00
|
|
|
|
}
|