using OSMDatastructure; using OSMDatastructure.Graph; using OSMImporter; namespace Pathfinding; public static class Pathfinder { public static ValueTuple> CustomAStar(string workingDir, Coordinates start, Coordinates goal, Tag.SpeedType vehicle) { DateTime startTime = DateTime.Now; TimeSpan calcTime; RegionManager regionManager = new (workingDir); OsmNode? startNode = ClosestNodeToCoordinates(start, vehicle, ref regionManager); OsmNode? goalNode = ClosestNodeToCoordinates(goal, vehicle, ref regionManager); if (startNode == null || goalNode == null) { calcTime = DateTime.Now - startTime; return new ValueTuple>(calcTime, new List()); } PriorityQueue toVisit = new(); toVisit.Enqueue(startNode, 0); startNode.currentPathWeight = 0; startNode.currentPathLength = 0; startNode.directDistanceToGoal = Utils.DistanceBetween(startNode, goalNode); bool stop = false; while (toVisit.Count > 0) { OsmNode closestNodeToGoal = toVisit.Dequeue(); //Console.WriteLine($"{toVisit.Count:000} {closestNodeToGoal.directDistanceToGoal:#.00} current:{closestNodeToGoal}"); foreach (OsmEdge edge in closestNodeToGoal.edges) { OsmNode? neighbor = regionManager.GetNode(edge.neighborId, edge.neighborRegion); if (neighbor is not null) { double newPotentialWeight = closestNodeToGoal.currentPathWeight + EdgeWeight(closestNodeToGoal, neighbor, edge.wayId, vehicle, ref regionManager); if (newPotentialWeight < neighbor.currentPathWeight) { neighbor.previousPathNode = closestNodeToGoal; neighbor.currentPathWeight = newPotentialWeight; neighbor.currentPathLength = closestNodeToGoal.currentPathLength + Utils.DistanceBetween(closestNodeToGoal, neighbor); neighbor.directDistanceToGoal = Utils.DistanceBetween(neighbor, goalNode); if (neighbor.Equals(goalNode) || closestNodeToGoal.directDistanceToGoal < 10) { stop = true; goalNode = neighbor; } else if(!stop) { toVisit.Enqueue(neighbor, neighbor.directDistanceToGoal); } } } } } List path = new(); OsmNode? currentNode = goalNode; while (currentNode is not null) { path.Add(PathNode.FromOsmNode(currentNode)!); currentNode = currentNode.previousPathNode; } path.Reverse(); calcTime = DateTime.Now - startTime; return new ValueTuple>(calcTime, path); } public static OsmNode? ClosestNodeToCoordinates(Coordinates coordinates, Tag.SpeedType vehicle, ref RegionManager regionManager) { OsmNode? closest = null; double distance = double.MaxValue; Region? region = regionManager.GetRegion(coordinates); if (region is null) return null; foreach (OsmNode node in region.nodes) { bool hasConnectionUsingVehicle = false; foreach (OsmEdge edge in node.edges) { double speed = GetSpeed(node, edge.wayId, vehicle, ref regionManager); if (speed != 0) hasConnectionUsingVehicle = true; } double nodeDistance = Utils.DistanceBetween(node, coordinates); if (nodeDistance < distance && hasConnectionUsingVehicle) { closest = node; distance = nodeDistance; } } return closest; } private static double GetSpeed(OsmNode node1, ulong wayId, Tag.SpeedType vehicle, ref RegionManager regionManager) { TagManager tags = regionManager.GetRegion(node1.coordinates)!.tagManager; Tag.WayType wayType = (Tag.WayType)tags.GetTag(wayId, Tag.TagType.highway)!; switch (vehicle) { case Tag.SpeedType.pedestrian: byte speed = Tag.defaultSpeedPedestrian[wayType]; if (speed is not 0) return speed; return 0; case Tag.SpeedType.car: case Tag.SpeedType.road: byte? maxSpeed = (byte?)tags.GetTag(wayId, Tag.TagType.maxspeed); if (maxSpeed is not null) return (double)maxSpeed; maxSpeed = Tag.defaultSpeedCar[wayType]; if(maxSpeed is not 0) return (byte)maxSpeed; return 0; default: return 0; } } private static double EdgeWeight(OsmNode node1, OsmNode node2, ulong wayId, Tag.SpeedType vehicle, ref RegionManager regionManager) { double distance = Utils.DistanceBetween(node1, node2); double speed = GetSpeed(node1, wayId, vehicle, ref regionManager); if (speed is not 0) return distance / speed; return double.PositiveInfinity; } }