Compare commits

..

17 Commits

Author SHA1 Message Date
6eab23ff16 bug: forgot to exchange from distance to weight during copy & paste 2023-04-09 17:47:45 +02:00
206f9c5811 Added weight and distance delta to pathnode 2023-04-09 17:46:35 +02:00
e0bb3ce3de Added Method for path-return (returns the path from current graph).
Added "tags" to return value for path.
2023-04-09 17:38:57 +02:00
2904be84f0 changed NONE speeds 2023-04-09 17:37:52 +02:00
13beaeaf73 removed old method, changed some speeds 2023-04-09 17:14:14 +02:00
ea7ce1f630 Changed/Fixed Namespaces 2023-04-09 17:06:45 +02:00
9c7fec1c37 Fixed infinity error JSON 2023-04-09 17:02:56 +02:00
5efec08bbc EdgeWeight rewrite 2023-04-09 17:00:28 +02:00
05ae0bff6e Fixed invalid json-type 2023-04-09 16:49:22 +02:00
8bd0c5a4d4 Added Pathfinding Time
Renamed GetRoute -> GetRouteTime and GetRouteDistance
2023-04-09 16:47:33 +02:00
feb9b70e50 Added Pathfinding Time 2023-04-09 16:47:30 +02:00
9ef0e421bc Moved Pathfinding ClosestNode and SpeedCalc to RegionManager (more appropriate).
Added validation if edge is valid connection for vehicle.
2023-04-09 16:41:42 +02:00
a54b189b08 Sorted Methods 2023-04-09 16:32:02 +02:00
585a9213ce Fixed result to correct time and no path error handling 2023-04-09 16:29:09 +02:00
bf08f38a1e fixed pathfinding namespace 2023-04-09 16:24:43 +02:00
fc5d388ecd Dispose of Region-filestream 2023-04-09 16:22:22 +02:00
58d1031524 Splitting Pathfinding into separate files for each type of routing.
Removing timetracking again from routing-algos (not in scope)
2023-04-09 16:17:15 +02:00
10 changed files with 284 additions and 187 deletions

View File

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
using OSMDatastructure;
using OSMDatastructure.Graph;
using OSMImporter;
using Pathfinding;
var builder = WebApplication.CreateBuilder(args);
@ -16,11 +16,20 @@ var app = builder.Build();
app.MapGet("/getRoute", (float latStart, float lonStart, float latEnd, float lonEnd) =>
app.MapGet("/getRouteDistance", (float latStart, float lonStart, float latEnd, float lonEnd) =>
{
ValueTuple<TimeSpan, List<PathNode>> result = Pathfinder.CustomAStar("D:/stuttgart-regbez-latest", new Coordinates(latStart, lonStart), new Coordinates(latEnd, lonEnd),
Tag.SpeedType.car);
PathResult pathResult = new PathResult(result.Item1, result.Item2);
DateTime startCalc = DateTime.Now;
List<PathNode> result = Pathfinder.AStarDistance("D:/stuttgart-regbez-latest", new Coordinates(latStart, lonStart), new Coordinates(latEnd, lonEnd));
PathResult pathResult = new PathResult(DateTime.Now - startCalc, result);
return pathResult;
}
);
app.MapGet("/getRouteTime", (float latStart, float lonStart, float latEnd, float lonEnd, Tag.SpeedType vehicle) =>
{
DateTime startCalc = DateTime.Now;
List<PathNode> result = Pathfinder.AStarTime("D:/stuttgart-regbez-latest", new Coordinates(latStart, lonStart), new Coordinates(latEnd, lonEnd), vehicle);
PathResult pathResult = new PathResult(DateTime.Now - startCalc, result);
return pathResult;
}
);
@ -28,7 +37,7 @@ app.MapGet("/getRoute", (float latStart, float lonStart, float latEnd, float lon
app.MapGet("/getClosestNode", (float lat, float lon) =>
{
RegionManager regionManager = new RegionManager("D:/stuttgart-regbez-latest");
return Pathfinder.ClosestNodeToCoordinates(new Coordinates(lat, lon), Tag.SpeedType.car, ref regionManager);
return regionManager.ClosestNodeToCoordinates(new Coordinates(lat, lon), Tag.SpeedType.road);
});
// Configure the HTTP request pipeline.
@ -49,15 +58,18 @@ app.Run();
internal class PathResult
{
[JsonInclude]public TimeSpan calcTime;
[JsonInclude] public double pathWeight;
[JsonInclude] public double pathTravelDistance;
[JsonInclude] public double pathWeight = double.MaxValue;
[JsonInclude] public double pathTravelDistance = double.MaxValue;
[JsonInclude]public List<PathNode> pathNodes;
public PathResult(TimeSpan calcTime, List<PathNode> pathNodes)
{
this.calcTime = calcTime;
this.pathNodes = pathNodes;
this.pathWeight = pathNodes.Last().currentPathWeight;
this.pathTravelDistance = pathNodes.Last().currentPathLength;
if (pathNodes.Count > 0)
{
this.pathWeight = pathNodes.Last().currentPathWeight;
this.pathTravelDistance = pathNodes.Last().currentPathLength;
}
}
}

View File

@ -1,4 +1,3 @@
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

View File

@ -59,16 +59,4 @@ public class Region
else return null;
}
public static Region? FromFile(string filePath)
{
if (File.Exists(filePath))
return JsonSerializer.Deserialize<Region>(new FileStream(filePath, FileMode.Open), serializerOptions)!;
else return null;
}
public static Region? FromId(string path, ulong regionId)
{
string filePath = Path.Join(path, $"{regionId}.region");
return FromFile(filePath);
}
}

View File

@ -1,7 +1,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace OSMDatastructure.Graph;
namespace OSMDatastructure;
[Serializable]
public class Tag
@ -40,25 +40,6 @@ public class Tag
this.value = value;
}
}
public static Tag FromBytes(byte[] bytes)
{
TagType type = (TagType)bytes[0];
dynamic value = false;
switch (type)
{
case TagType.highway:
case TagType.oneway:
case TagType.forward:
value = BitConverter.ToBoolean(bytes, 1);
break;
case TagType.maxspeed:
value = bytes[1];
break;
}
return new Tag(type, value);
}
public static Tag? ConvertToTag(string key, string value)
{
@ -116,7 +97,7 @@ public class Tag
}
public static readonly Dictionary<WayType, byte> defaultSpeedCar = new() {
{ WayType.NONE, 1 },
{ WayType.NONE, 0 },
{ WayType.motorway, 110 },
{ WayType.trunk, 100 },
{ WayType.primary, 80 },
@ -129,7 +110,7 @@ public class Tag
{ WayType.primary_link, 30 },
{ WayType.secondary_link, 25 },
{ WayType.tertiary_link, 25 },
{ WayType.living_street, 10 },
{ WayType.living_street, 5 },
{ WayType.service, 1 },
{ WayType.pedestrian, 0 },
{ WayType.track, 15 },
@ -168,17 +149,19 @@ public class Tag
{ WayType.bus_guideway, 0 },
{ WayType.escape, 1 },
{ WayType.raceway, 0 },
{ WayType.road, 3 },
{ WayType.road, 2 },
{ WayType.busway, 0 },
{ WayType.footway, 4 },
{ WayType.bridleway, 1 },
{ WayType.steps, 2 },
{ WayType.corridor, 3 },
{ WayType.path, 4 },
{ WayType.cycleway, 2 },
{ WayType.cycleway, 1 },
{ WayType.construction, 0 }
};
// ReSharper disable InconsistentNaming
public enum WayType : byte { NONE, motorway, trunk, primary, secondary, tertiary, unclassified, residential, motorway_link, trunk_link, primary_link, secondary_link, tertiary_link, living_street, service, pedestrian, track, bus_guideway, escape, raceway, road, busway, footway, bridleway, steps, corridor, path, cycleway, construction }
// ReSharper restore InconsistentNaming
public enum SpeedType { pedestrian, car, road }
}

View File

@ -1,6 +1,7 @@
using System.Text.Json.Serialization;
using OSMDatastructure.Graph;
namespace OSMDatastructure.Graph;
namespace OSMDatastructure;
[Serializable]
public class TagManager

View File

@ -1,14 +1,18 @@
using System.Text.Json.Serialization;
using OSMDatastructure;
using OSMDatastructure.Graph;
namespace Pathfinding;
public class PathNode : OsmNode
{
[JsonInclude]public new double currentPathWeight = double.MaxValue;
[JsonInclude]public new double currentPathLength = double.MaxValue;
[JsonInclude]public new double directDistanceToGoal = double.MaxValue;
[JsonInclude]public new double currentPathLength = double.MaxValue;
[JsonInclude]public double distanceBetweenNodes = double.MaxValue;
[JsonInclude]public new double currentPathWeight = double.MaxValue;
[JsonInclude]public double weightBetweenNodes = double.MaxValue;
[JsonInclude]public Dictionary<string, string> tags = new();
public PathNode(ulong nodeId, float lat, float lon) : base(nodeId, lat, lon)
{
}
@ -17,16 +21,24 @@ public class PathNode : OsmNode
{
}
public static PathNode? FromOsmNode(OsmNode? node)
public static PathNode? FromOsmNode(OsmNode? node, HashSet<Tag>? tags, double distance, double weight)
{
if (node is null)
return null;
PathNode retNode = new PathNode(node.nodeId, node.coordinates)
PathNode retNode = new(node.nodeId, node.coordinates)
{
currentPathLength = node.currentPathLength,
currentPathWeight = node.currentPathWeight,
directDistanceToGoal = node.directDistanceToGoal
currentPathWeight = double.IsPositiveInfinity(node.currentPathWeight) ? double.MaxValue : node.currentPathWeight,
directDistanceToGoal = node.directDistanceToGoal,
distanceBetweenNodes = distance,
weightBetweenNodes = weight
};
if (tags != null)
foreach (Tag tag in tags)
{
retNode.tags.Add(tag.key.ToString(), tag.value.ToString());
}
return retNode;
}
}

View File

@ -1,135 +1,60 @@
using OSMDatastructure;
using OSMDatastructure.Graph;
using OSMImporter;
namespace Pathfinding;
public static class Pathfinder
public static partial class Pathfinder
{
public static ValueTuple<TimeSpan, List<PathNode>> CustomAStar(string workingDir, Coordinates start, Coordinates goal, Tag.SpeedType vehicle)
private static ValueTuple<OsmNode?, OsmNode?> SetupNodes(Coordinates startCoordinates, Coordinates goalCoordinates, RegionManager regionManager )
{
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<TimeSpan, List<PathNode>>(calcTime, new List<PathNode>());
}
ValueTuple<OsmNode?, OsmNode?> retTuple = new();
retTuple.Item1 = regionManager.ClosestNodeToCoordinates(startCoordinates, Tag.SpeedType.road);
retTuple.Item2 = regionManager.ClosestNodeToCoordinates(goalCoordinates, Tag.SpeedType.road);
if (retTuple.Item1 is null || retTuple.Item2 is null)
return retTuple;
retTuple.Item1.currentPathWeight = 0;
retTuple.Item1.currentPathLength = 0;
retTuple.Item1.directDistanceToGoal = Utils.DistanceBetween(retTuple.Item1, retTuple.Item2);
return retTuple;
}
PriorityQueue<OsmNode, double> 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);
}
}
}
}
}
private static double EdgeWeight(OsmNode node1, OsmEdge edge, Tag.SpeedType vehicle, RegionManager regionManager)
{
OsmNode? node2 = regionManager.GetNode(edge.neighborId, edge.neighborRegion);
if (node2 is null)
return double.MaxValue;
double distance = Utils.DistanceBetween(node1, node2);
double speed = regionManager.GetSpeedForEdge(node1, edge.wayId, vehicle);
if (speed is 0)
return double.MaxValue;
return distance / speed;
}
private static List<PathNode> GetRouteFromCalc(OsmNode goalNode, RegionManager regionManager)
{
List<PathNode> path = new();
OsmNode? currentNode = goalNode;
while (currentNode is not null)
{
path.Add(PathNode.FromOsmNode(currentNode)!);
HashSet<Tag>? tags = null;
double distance = 0;
double weight = 0;
if (currentNode.previousPathNode is not null)
{
OsmEdge edge = currentNode.previousPathNode!.edges.First(e => e.neighborId.Equals(currentNode.nodeId));
tags = regionManager.GetRegion(currentNode.coordinates)!.tagManager.GetTagsForWayId(edge.wayId);
distance = currentNode.currentPathLength - currentNode.previousPathNode.currentPathLength;
weight = currentNode.currentPathWeight - currentNode.previousPathNode.currentPathWeight;
}
PathNode? pn = PathNode.FromOsmNode(currentNode, tags, distance, weight);
if(pn is not null)
path.Add(pn!);
currentNode = currentNode.previousPathNode;
}
path.Reverse();
calcTime = DateTime.Now - startTime;
return new ValueTuple<TimeSpan, List<PathNode>>(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;
return path;
}
}

View File

@ -0,0 +1,51 @@
using OSMDatastructure;
using OSMDatastructure.Graph;
namespace Pathfinding;
public static partial class Pathfinder
{
public static List<PathNode> AStarDistance(string workingDir, Coordinates start,
Coordinates goal)
{
RegionManager regionManager = new (workingDir);
ValueTuple<OsmNode?, OsmNode?> startAndEndNode = SetupNodes(start, goal, regionManager);
if (startAndEndNode.Item1 is null || startAndEndNode.Item2 is null)
return new List<PathNode>();
OsmNode goalNode = startAndEndNode.Item2!;
PriorityQueue<OsmNode, double> toVisit = new();
toVisit.Enqueue(startAndEndNode.Item1, 0);
bool stop = false;
while (toVisit.Count > 0)
{
OsmNode closestNodeToGoal = toVisit.Dequeue();
foreach (OsmEdge edge in closestNodeToGoal.edges)
{
OsmNode? neighbor = regionManager.GetNode(edge.neighborId, edge.neighborRegion);
if (neighbor is not null)
{
double newPotentialLength = closestNodeToGoal.currentPathLength + Utils.DistanceBetween(closestNodeToGoal, neighbor);
if (newPotentialLength < neighbor.currentPathLength)
{
neighbor.previousPathNode = closestNodeToGoal;
neighbor.currentPathLength = newPotentialLength;
neighbor.directDistanceToGoal = Utils.DistanceBetween(neighbor, goalNode);
if (neighbor.Equals(goalNode))
{
stop = true;
}
else if(!stop)
{
toVisit.Enqueue(neighbor, neighbor.directDistanceToGoal);
}
}
}
}
}
return GetRouteFromCalc(goalNode, regionManager);
}
}

View File

@ -0,0 +1,55 @@
using OSMDatastructure;
using OSMDatastructure.Graph;
using Utils = OSMDatastructure.Utils;
namespace Pathfinding;
public static partial class Pathfinder
{
public static List<PathNode> AStarTime(string workingDir, Coordinates start,
Coordinates goal, Tag.SpeedType vehicle)
{
RegionManager regionManager = new (workingDir);
ValueTuple<OsmNode?, OsmNode?> startAndEndNode = SetupNodes(start, goal, regionManager);
if (startAndEndNode.Item1 is null || startAndEndNode.Item2 is null)
return new List<PathNode>();
OsmNode goalNode = startAndEndNode.Item2!;
PriorityQueue<OsmNode, double> toVisit = new();
toVisit.Enqueue(startAndEndNode.Item1, 0);
bool stop = false;
while (toVisit.Count > 0)
{
OsmNode closestNodeToGoal = toVisit.Dequeue();
foreach (OsmEdge edge in closestNodeToGoal.edges.Where(
edge => regionManager.TestValidConnectionForType(closestNodeToGoal, edge, vehicle)))
{
OsmNode? neighbor = regionManager.GetNode(edge.neighborId, edge.neighborRegion);
if (neighbor is not null)
{
double newPotentialWeight = closestNodeToGoal.currentPathWeight +
EdgeWeight(closestNodeToGoal, edge, vehicle, regionManager);
if (newPotentialWeight < neighbor.currentPathWeight)
{
neighbor.previousPathNode = closestNodeToGoal;
neighbor.currentPathLength = closestNodeToGoal.currentPathLength + Utils.DistanceBetween(closestNodeToGoal, neighbor);
neighbor.currentPathWeight = newPotentialWeight;
neighbor.directDistanceToGoal = Utils.DistanceBetween(neighbor, goalNode);
if (neighbor.Equals(goalNode))
{
stop = true;
}
else if(!stop)
{
toVisit.Enqueue(neighbor, neighbor.directDistanceToGoal);
}
}
}
}
}
return GetRouteFromCalc(goalNode, regionManager);
}
}

View File

@ -1,7 +1,8 @@
using System.Text.Json;
using OSMDatastructure;
using OSMDatastructure.Graph;
namespace OSMImporter
namespace Pathfinding
{
public class RegionManager
{
@ -24,7 +25,7 @@ namespace OSMImporter
return value;
else
{
Region? loadedRegion = LoadRegion(id);
Region? loadedRegion = RegionFromId(id);
if(loadedRegion is not null)
_regions.Add(loadedRegion!.regionHash, value: loadedRegion);
return loadedRegion;
@ -36,23 +37,22 @@ namespace OSMImporter
return this._regions.Values.ToArray();
}
private Region? LoadRegion(Coordinates coordinates)
private Region? RegionFromFile(string filePath)
{
return LoadRegion(Coordinates.GetRegionHashCode(coordinates));
Region? retRegion = null;
if (File.Exists(filePath))
{
FileStream regionFile = new FileStream(filePath, FileMode.Open);
retRegion = JsonSerializer.Deserialize<Region>(regionFile, Region.serializerOptions)!;
regionFile.Dispose();
}
return retRegion;
}
private Region? LoadRegion(ulong id)
private Region? RegionFromId(ulong regionId)
{
Console.WriteLine($"Load Region {id}");
return Region.FromId(workingDirectory, id);
}
public OsmNode? GetNode(Coordinates coordinates)
{
Region? regionWithNode = GetRegion(coordinates);
if (regionWithNode is not null)
return regionWithNode.GetNode(coordinates);
else return null;
string filePath = Path.Join(workingDirectory, $"{regionId}.region");
return RegionFromFile(filePath);
}
public OsmNode? GetNode(ulong nodeId, ulong regionId)
@ -60,5 +60,76 @@ namespace OSMImporter
Region? r = GetRegion(regionId);
return r?.GetNode(nodeId);
}
public bool TestValidConnectionForType(OsmNode node1, OsmNode node2, Tag.SpeedType type)
{
foreach (OsmEdge edge in node1.edges)
{
if (edge.neighborId.Equals(node2.nodeId))
return TestValidConnectionForType(node1, edge, type);
}
return false;
}
public bool TestValidConnectionForType(OsmNode node1, OsmEdge edge, Tag.SpeedType type)
{
double speed = GetSpeedForEdge(node1, edge.wayId, type);
if (speed != 0)
return true;
return false;
}
public OsmNode? ClosestNodeToCoordinates(Coordinates coordinates, Tag.SpeedType vehicle)
{
OsmNode? closest = null;
double distance = double.MaxValue;
Region? region = 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 = GetSpeedForEdge(node, edge.wayId, vehicle);
if (speed != 0)
hasConnectionUsingVehicle = true;
}
double nodeDistance = Utils.DistanceBetween(node, coordinates);
if (nodeDistance < distance && hasConnectionUsingVehicle)
{
closest = node;
distance = nodeDistance;
}
}
return closest;
}
public double GetSpeedForEdge(OsmNode node1, ulong wayId, Tag.SpeedType vehicle)
{
TagManager tags = 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;
}
}
}
}