Compare commits
30 Commits
ec6725a5c5
...
master
Author | SHA1 | Date | |
---|---|---|---|
56ac9dc948 | |||
9ef63c9886 | |||
2b5dd91335 | |||
9f0d47ed59 | |||
371989b34d | |||
e53d1086cc | |||
dc98fb51b1 | |||
3077b4d8b8 | |||
7c5d87ca76 | |||
a5f272dfb9 | |||
f84aa82186 | |||
6d59253a0b | |||
af821a761f | |||
9b88996439 | |||
5733b0edb3 | |||
923cbee280 | |||
f19aa0007e | |||
f525b88a3a | |||
d497196f9f | |||
c705fdb63a | |||
aa05aad5b3 | |||
7d33d11a03 | |||
edd931bca5 | |||
6b5dddb1e3 | |||
fea0ecf17b | |||
30b29aa25c | |||
73e7daffd7 | |||
7b88616373 | |||
2799db162d | |||
9301e948b0 |
@ -16,7 +16,7 @@ var app = builder.Build();
|
||||
app.MapGet("/getRoute", (float latStart, float lonStart, float latEnd, float lonEnd, Tag.SpeedType vehicle, double useHigherLevelRoadsPriority, double maxTurnAngle) =>
|
||||
{
|
||||
Pathfinder result = new Pathfinder("D:/stuttgart-regbez-latest", useHigherLevelRoadsPriority, maxTurnAngle).AStar(new Coordinates(latStart, lonStart),
|
||||
new Coordinates(latEnd, lonEnd), vehicle);
|
||||
new Coordinates(latEnd, lonEnd), vehicle, 3);
|
||||
return result.pathResult;
|
||||
}
|
||||
);
|
||||
@ -24,7 +24,7 @@ app.MapGet("/getRoute", (float latStart, float lonStart, float latEnd, float lon
|
||||
app.MapGet("/getShortestRoute", (float latStart, float lonStart, float latEnd, float lonEnd) =>
|
||||
{
|
||||
Pathfinder result = new Pathfinder("D:/stuttgart-regbez-latest", 0, 30).AStar(new Coordinates(latStart, lonStart),
|
||||
new Coordinates(latEnd, lonEnd), Tag.SpeedType.any);
|
||||
new Coordinates(latEnd, lonEnd), Tag.SpeedType.any, 3);
|
||||
return result.pathResult;
|
||||
}
|
||||
);
|
||||
|
@ -21,8 +21,7 @@ public class Coordinates
|
||||
if (obj == null || obj.GetType() != this.GetType())
|
||||
return false;
|
||||
Coordinates convObj = (Coordinates)obj;
|
||||
// ReSharper disable twice CompareOfFloatsByEqualityOperator static values
|
||||
return convObj.latitude == this.latitude && convObj.longitude == this.longitude;
|
||||
return convObj.latitude.Equals(this.latitude) && convObj.longitude.Equals(longitude);
|
||||
}
|
||||
|
||||
public static ulong GetRegionHashCode(float latitude, float longitude)
|
||||
@ -49,6 +48,6 @@ public class Coordinates
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"lat:{latitude.ToString(NumberFormatInfo.InvariantInfo)} lon:{longitude.ToString(CultureInfo.InvariantCulture)}";
|
||||
$"Coordinates lat:{latitude.ToString(NumberFormatInfo.InvariantInfo)} lon:{longitude.ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
@ -22,6 +22,6 @@ public class OsmEdge
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"w:{wayId} n1:{startId} n2:{neighborId} in r:{neighborRegion}";
|
||||
return $"Edge wayId:{wayId} n1:{startId} n2:{neighborId} in regionId:{neighborRegion}";
|
||||
}
|
||||
}
|
@ -40,6 +40,6 @@ public class OsmNode
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nodeId} {coordinates} ec:{edges.Count}";
|
||||
return $"Node id:{nodeId} coordinates:{coordinates} edges-count:{edges.Count}";
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ public class Tag
|
||||
switch (key)
|
||||
{
|
||||
case TagType.highway:
|
||||
this.value = (WayType)value.GetByte();
|
||||
this.value = value.GetByte();
|
||||
break;
|
||||
case TagType.maxspeed:
|
||||
this.value = value.GetByte();
|
||||
@ -61,6 +61,7 @@ public class Tag
|
||||
}
|
||||
break;
|
||||
case "maxspeed":
|
||||
case "maxspeed:max":
|
||||
try
|
||||
{
|
||||
byte speed = Convert.ToByte(value);
|
||||
@ -111,8 +112,8 @@ public class Tag
|
||||
|
||||
public static readonly Dictionary<WayType, byte> defaultSpeedCar = new() {
|
||||
{ WayType.NONE, 0 },
|
||||
{ WayType.motorway, 100 },
|
||||
{ WayType.motorroad, 90 },
|
||||
{ WayType.motorway, 130 },
|
||||
{ WayType.motorroad, 100 },
|
||||
{ WayType.trunk, 85 },
|
||||
{ WayType.primary, 65 },
|
||||
{ WayType.secondary, 60 },
|
||||
|
@ -31,7 +31,7 @@ public class Pathfinder
|
||||
this.turnAngle = turnAngle;
|
||||
}
|
||||
|
||||
public Pathfinder AStar(Coordinates startCoordinates, Coordinates goalCoordinates, SpeedType vehicle)
|
||||
public Pathfinder AStar(Coordinates startCoordinates, Coordinates goalCoordinates, SpeedType vehicle, double extraTime)
|
||||
{
|
||||
DateTime startCalc = DateTime.Now;
|
||||
_speedType = vehicle;
|
||||
@ -62,15 +62,12 @@ public class Pathfinder
|
||||
{
|
||||
firstFound = DateTime.Now - startCalc;
|
||||
found = true;
|
||||
Console.WriteLine($"First: {firstFound} Multiplied by {extraTime}: {firstFound.Multiply(extraTime)}");
|
||||
}
|
||||
maxGscore = gScore[goalNode];
|
||||
/*
|
||||
int removed = openSetfScore.Remove(gScore.Where(item => item.Value > maxGscore)
|
||||
.ToDictionary(item => item.Key, item => item.Value).Keys);
|
||||
Console.WriteLine($"Removed {removed}");*/
|
||||
}
|
||||
|
||||
if (found && DateTime.Now - startCalc > firstFound.Multiply(2))
|
||||
if (found && DateTime.Now - startCalc > firstFound.Multiply(extraTime))
|
||||
stop = true;
|
||||
|
||||
foreach (OsmEdge edge in currentNode.edges)
|
||||
@ -136,8 +133,12 @@ public class Pathfinder
|
||||
if (priority == 0)
|
||||
return double.MaxValue;
|
||||
|
||||
double distance = Utils.DistanceBetween(neighborNode, goalNode);
|
||||
|
||||
double speed = regionManager.GetSpeedForEdge(currentNode, edge.wayId, _speedType);
|
||||
|
||||
double roadPriority = priority * roadPriorityFactor;
|
||||
|
||||
double angle = 0;
|
||||
if (_cameFromDict!.ContainsKey(currentNode))
|
||||
{
|
||||
@ -151,12 +152,13 @@ public class Pathfinder
|
||||
angle = nodeAngle / 180;
|
||||
}
|
||||
|
||||
double roadPriority = priority * roadPriorityFactor;
|
||||
return Utils.DistanceBetween(neighborNode, goalNode) / (speed * angle + roadPriority + 1);
|
||||
return distance / (speed * angle + roadPriority + 1);
|
||||
}
|
||||
|
||||
public void SaveResult(string path)
|
||||
{
|
||||
if(File.Exists(path))
|
||||
File.Delete(path);
|
||||
FileStream fs = new (path, FileMode.CreateNew);
|
||||
JsonSerializer.Serialize(fs, pathResult, JsonSerializerOptions.Default);
|
||||
fs.Dispose();
|
||||
|
@ -3,7 +3,7 @@ namespace Pathfinding;
|
||||
public class RPriorityQueue<TKey, TPriority> where TKey : notnull
|
||||
{
|
||||
public Dictionary<TKey, TPriority> queue;
|
||||
public int Count { get; private set; }
|
||||
public int Count => queue.Count;
|
||||
|
||||
public RPriorityQueue()
|
||||
{
|
||||
@ -14,14 +14,12 @@ public class RPriorityQueue<TKey, TPriority> where TKey : notnull
|
||||
{
|
||||
if (!queue.TryAdd(key, priority))
|
||||
queue[key] = priority;
|
||||
Count = queue.Count;
|
||||
}
|
||||
|
||||
public TKey? Dequeue()
|
||||
public TKey Dequeue()
|
||||
{
|
||||
TKey? retKey = queue.MinBy(item => item.Value).Key;
|
||||
TKey retKey = queue.MinBy(item => item.Value).Key;
|
||||
queue.Remove(retKey);
|
||||
Count = queue.Count;
|
||||
return retKey;
|
||||
}
|
||||
|
||||
@ -30,7 +28,20 @@ public class RPriorityQueue<TKey, TPriority> where TKey : notnull
|
||||
int before = Count;
|
||||
queue = queue.Where(queueitem => !elements.Contains(queueitem.Key))
|
||||
.ToDictionary(item => item.Key, item => item.Value);
|
||||
Count = queue.Count;
|
||||
return before - Count;
|
||||
}
|
||||
|
||||
public int RemoveExcept(IEnumerable<TKey> exceptKeys)
|
||||
{
|
||||
int before = Count;
|
||||
queue = queue.IntersectBy(exceptKeys, item => item.Key).ToDictionary(item => item.Key, item => item.Value);
|
||||
return before - Count;
|
||||
}
|
||||
|
||||
public int Clear()
|
||||
{
|
||||
int before = Count;
|
||||
queue.Clear();
|
||||
return before;
|
||||
}
|
||||
}
|
@ -23,17 +23,13 @@ namespace Pathfinding
|
||||
|
||||
public Region? GetRegion(ulong id)
|
||||
{
|
||||
|
||||
if(_regions.TryGetValue(id, out Region? retRegion))
|
||||
return retRegion;
|
||||
|
||||
if (!_regions.ContainsKey(id))
|
||||
{
|
||||
Region? loadedRegion = RegionFromId(id);
|
||||
|
||||
if(_regions.TryGetValue(id, out Region? retRegion2)) //Prevent other thread from changing collection
|
||||
return retRegion2;
|
||||
if (loadedRegion is not null)
|
||||
_regions.TryAdd(loadedRegion.regionHash, loadedRegion);
|
||||
|
||||
return _regions[id]; //return from _regions instead of loadedRegion for multithreading/pointers
|
||||
}
|
||||
return _regions[id];
|
||||
}
|
||||
|
||||
@ -45,7 +41,10 @@ namespace Pathfinding
|
||||
private static Region? RegionFromFile(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
//throw new FileNotFoundException(filePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
FileStream regionFile = new (filePath, FileMode.Open, FileAccess.Read, FileShare.Read, (int)new FileInfo(filePath).Length, FileOptions.SequentialScan);
|
||||
Region retRegion = JsonSerializer.Deserialize<Region>(regionFile, Region.serializerOptions)!;
|
||||
@ -67,9 +66,8 @@ namespace Pathfinding
|
||||
|
||||
public bool TestValidConnectionForType(OsmNode node1, OsmNode node2, SpeedType type)
|
||||
{
|
||||
foreach (OsmEdge edge in node1.edges)
|
||||
foreach (OsmEdge edge in node1.edges.Where(edge => edge.neighborId.Equals(node2.nodeId)))
|
||||
{
|
||||
if (edge.neighborId.Equals(node2.nodeId))
|
||||
return TestValidConnectionForType(node1, edge, type);
|
||||
}
|
||||
|
||||
@ -151,7 +149,7 @@ namespace Pathfinding
|
||||
case WayType.motorway:
|
||||
case WayType.motorway_link:
|
||||
case WayType.motorroad:
|
||||
return 17;
|
||||
return 20;
|
||||
case WayType.trunk:
|
||||
case WayType.trunk_link:
|
||||
case WayType.primary:
|
||||
@ -159,10 +157,9 @@ namespace Pathfinding
|
||||
return 10;
|
||||
case WayType.secondary:
|
||||
case WayType.secondary_link:
|
||||
return 7;
|
||||
case WayType.tertiary:
|
||||
case WayType.tertiary_link:
|
||||
return 5;
|
||||
return 6;
|
||||
case WayType.unclassified:
|
||||
case WayType.residential:
|
||||
case WayType.road:
|
||||
|
33
RenderPath/Bounds.cs
Normal file
33
RenderPath/Bounds.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using OSMDatastructure.Graph;
|
||||
|
||||
namespace RenderPath;
|
||||
|
||||
public class Bounds
|
||||
{
|
||||
[JsonInclude]public float minLat, maxLat, minLon, maxLon;
|
||||
|
||||
[JsonConstructor]
|
||||
public Bounds(float minLat, float minLon, float maxLat, float maxLon)
|
||||
{
|
||||
this.minLon = minLon;
|
||||
this.maxLat = maxLat;
|
||||
this.maxLon = maxLon;
|
||||
this.minLat = minLat;
|
||||
}
|
||||
|
||||
public static Bounds FromCoords(float lat1, float lon1, float lat2, float lon2)
|
||||
{
|
||||
float minLat = lat1 < lat2 ? lat1 : lat2;
|
||||
float minLon = lon1 < lon2 ? lon1 : lon2;
|
||||
float maxLat = lat1 > lat2 ? lat1 : lat2;
|
||||
float maxLon = lon1 > lon2 ? lon1 : lon2;
|
||||
|
||||
return new Bounds(minLat, minLon, maxLat, maxLon);
|
||||
}
|
||||
|
||||
public static Bounds FromCoords(Coordinates c1, Coordinates c2)
|
||||
{
|
||||
return FromCoords(c1.latitude, c1.longitude, c2.latitude, c2.longitude);
|
||||
}
|
||||
}
|
44
RenderPath/PNGRenderer.cs
Normal file
44
RenderPath/PNGRenderer.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
|
||||
namespace RenderPath;
|
||||
|
||||
#pragma warning disable CA1416
|
||||
public class PNGRenderer : Renderer
|
||||
{
|
||||
private readonly Image _image;
|
||||
private readonly Graphics _graphics;
|
||||
|
||||
public PNGRenderer(int width, int height)
|
||||
{
|
||||
_image = new Bitmap(width, height, PixelFormat.Format32bppPArgb);
|
||||
_graphics = Graphics.FromImage(_image);
|
||||
_graphics.Clear(Color.White);
|
||||
}
|
||||
|
||||
public PNGRenderer(Image renderOver)
|
||||
{
|
||||
_image = renderOver;
|
||||
_graphics = Graphics.FromImage(renderOver);
|
||||
}
|
||||
|
||||
public override void DrawLine(float x1, float y1, float x2, float y2, int width, Color color)
|
||||
{
|
||||
Pen p = new Pen(color, width);
|
||||
_graphics.DrawLine(p, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
public override void DrawDot(float x, float y, int radius, Color color)
|
||||
{
|
||||
Brush b = new SolidBrush(color);
|
||||
x -= radius / 2f;
|
||||
y -= radius / 2f;
|
||||
_graphics.FillEllipse(b, x, y, radius, radius);
|
||||
}
|
||||
|
||||
public override void Save(string path)
|
||||
{
|
||||
_image.Save($"{path}.png", ImageFormat.Png);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1416
|
@ -1,50 +1,29 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using OSMDatastructure;
|
||||
using OSMDatastructure.Graph;
|
||||
using Pathfinding;
|
||||
|
||||
namespace RenderPath;
|
||||
|
||||
public static class Renderer
|
||||
public abstract class Renderer
|
||||
{
|
||||
private const int ImageMaxSize = 20000;
|
||||
private const float PenThickness = 2;
|
||||
private const int PenThickness = 2;
|
||||
private static readonly Color RouteColor = Color.Red;
|
||||
private static readonly Color WeightStartColor = Color.FromArgb(127, 0, 100, 255);
|
||||
private static readonly Color WeightEndColor = Color.FromArgb(255, 0, 255, 0);
|
||||
private static readonly Color RoadPrioStart = Color.FromArgb(200, 100, 100, 100);
|
||||
private static readonly Color RoadPrioEnd = Color.FromArgb(255, 255, 180, 0);
|
||||
|
||||
public class Bounds
|
||||
{
|
||||
public readonly float minLat, maxLat, minLon, maxLon;
|
||||
public Bounds? bounds;
|
||||
|
||||
public Bounds(float minLat, float minLon, float maxLat, float maxLon)
|
||||
{
|
||||
this.minLon = minLon;
|
||||
this.maxLat = maxLat;
|
||||
this.maxLon = maxLon;
|
||||
this.minLat = minLat;
|
||||
}
|
||||
}
|
||||
public enum RenderType { png, svg}
|
||||
|
||||
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
|
||||
public static Image DrawPathfinder(Pathfinder pathfinder)
|
||||
{
|
||||
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);
|
||||
public abstract void DrawLine(float x1, float y1, float x2, float y2, int width, Color color);
|
||||
public abstract void DrawDot(float x, float y, int r, Color color);
|
||||
public abstract void Save(string path);
|
||||
|
||||
return areaGScorePathRender.Item1;
|
||||
}
|
||||
|
||||
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
|
||||
public static ValueTuple<Image, Bounds> DrawArea(RegionManager rm)
|
||||
public static Renderer DrawArea(RegionManager rm, RenderType renderType)
|
||||
{
|
||||
HashSet<OsmNode> nodes = new();
|
||||
foreach (OSMDatastructure.Region r in rm.GetAllRegions())
|
||||
@ -63,43 +42,49 @@ public static class Renderer
|
||||
int pixelsX = (int)(lonDiff * scaleFactor);
|
||||
int pixelsY = (int)(latDiff * scaleFactor);
|
||||
|
||||
Image ret = new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppPArgb);
|
||||
Graphics g = Graphics.FromImage(ret);
|
||||
g.Clear(Color.White);
|
||||
Renderer renderer;
|
||||
switch (renderType)
|
||||
{
|
||||
case RenderType.svg:
|
||||
renderer = new SVGRenderer(pixelsX, pixelsY);
|
||||
break;
|
||||
default:
|
||||
renderer = new PNGRenderer(pixelsX, pixelsY);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (OsmNode node in nodes)
|
||||
{
|
||||
foreach (OsmEdge edge in node.edges)
|
||||
{
|
||||
double priority = rm.GetPriorityForVehicle(Tag.SpeedType.car, edge, node) / 17;
|
||||
double priority = rm.GetPriorityForVehicle(Tag.SpeedType.car, edge, node) / 20;
|
||||
Coordinates c1 = node.coordinates;
|
||||
OsmNode nNode = rm.GetNode(edge.neighborId, edge.neighborRegion)!;
|
||||
Coordinates c2 = nNode.coordinates;
|
||||
|
||||
Pen p = new(ColorInterp(RoadPrioStart, RoadPrioEnd, priority), PenThickness);
|
||||
float x1 = (c1.longitude - minLon) * scaleFactor;
|
||||
float y1 = (maxLat - c1.latitude) * scaleFactor;
|
||||
float x2 = (c2.longitude - minLon) * scaleFactor;
|
||||
float y2 = (maxLat - c2.latitude) * scaleFactor;
|
||||
|
||||
g.DrawLine(p, x1, y1, x2, y2);
|
||||
renderer.DrawLine(x1, y1, x2, y2, PenThickness, ColorInterp(RoadPrioStart, RoadPrioEnd, priority));
|
||||
}
|
||||
}
|
||||
|
||||
return new ValueTuple<Image, Bounds>(ret, new Bounds(minLat,minLon,maxLat,maxLon));
|
||||
renderer.bounds = new Bounds(minLat,minLon,maxLat,maxLon);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
|
||||
public static ValueTuple<Image, Bounds> DrawPath(PathResult pathResult, Image? renderOver = null, Bounds? bounds = null)
|
||||
public static Renderer DrawPath(PathResult pathResult, RenderType renderType, Renderer? drawOver)
|
||||
{
|
||||
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 minLat = drawOver?.bounds!.minLat ?? coordinates.Min(coords => coords.latitude);
|
||||
float minLon = drawOver?.bounds!.minLon ?? coordinates.Min(coords => coords.longitude);
|
||||
float maxLat = drawOver?.bounds!.maxLat ?? coordinates.Max(coords => coords.latitude);
|
||||
float maxLon = drawOver?.bounds!.maxLon ?? coordinates.Max(coords => coords.longitude);
|
||||
|
||||
float latDiff = maxLat - minLat;
|
||||
float lonDiff = maxLon - minLon;
|
||||
@ -109,36 +94,43 @@ public static class Renderer
|
||||
int pixelsX = (int)(lonDiff * scaleFactor);
|
||||
int pixelsY = (int)(latDiff * scaleFactor);
|
||||
|
||||
Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppPArgb);
|
||||
Graphics g = Graphics.FromImage(ret);
|
||||
if(renderOver is null)
|
||||
g.Clear(Color.White);
|
||||
|
||||
Pen p = new Pen(RouteColor, PenThickness);
|
||||
Renderer renderer;
|
||||
if(drawOver is null)
|
||||
switch (renderType)
|
||||
{
|
||||
case RenderType.svg:
|
||||
renderer = new SVGRenderer(pixelsX, pixelsY);
|
||||
break;
|
||||
default:
|
||||
renderer = new PNGRenderer(pixelsX, pixelsY);
|
||||
break;
|
||||
}
|
||||
else
|
||||
renderer = drawOver;
|
||||
|
||||
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);
|
||||
float x1 = (c1.longitude - minLon) * scaleFactor;
|
||||
float y1 = (maxLat - c1.latitude) * scaleFactor;
|
||||
float x2 = (c2.longitude - minLon) * scaleFactor;
|
||||
float y2 = (maxLat - c2.latitude) * scaleFactor;
|
||||
|
||||
renderer.DrawLine(x1, y1, x2, y2, PenThickness, RouteColor);
|
||||
}
|
||||
|
||||
return new ValueTuple<Image, Bounds>(ret, new Bounds(minLat,minLon,maxLat,maxLon));
|
||||
renderer.bounds = new Bounds(minLat, minLon, maxLat, maxLon);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
[SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")]
|
||||
public static ValueTuple<Image, Bounds> DrawGScores(Dictionary<OsmNode, double> gScore, Image? renderOver = null,
|
||||
Bounds? bounds = null)
|
||||
public static Renderer DrawGScores(Dictionary<OsmNode, double> gScore, RenderType renderType, Renderer? drawOver)
|
||||
{
|
||||
|
||||
float minLat = bounds?.minLat ?? gScore.Min(kv => kv.Key.coordinates.latitude);
|
||||
float minLon = bounds?.minLon ?? gScore.Min(kv => kv.Key.coordinates.longitude);
|
||||
float maxLat = bounds?.maxLat ?? gScore.Max(kv => kv.Key.coordinates.latitude);
|
||||
float maxLon = bounds?.maxLon ?? gScore.Max(kv => kv.Key.coordinates.longitude);
|
||||
float minLat = drawOver?.bounds!.minLat ?? gScore.Min(kv => kv.Key.coordinates.latitude);
|
||||
float minLon = drawOver?.bounds!.minLon ?? gScore.Min(kv => kv.Key.coordinates.longitude);
|
||||
float maxLat = drawOver?.bounds!.maxLat ?? gScore.Max(kv => kv.Key.coordinates.latitude);
|
||||
float maxLon = drawOver?.bounds!.maxLon ?? gScore.Max(kv => kv.Key.coordinates.longitude);
|
||||
|
||||
double minWeight = gScore.Min(kv => kv.Value);
|
||||
double maxWeight = gScore.Max(kv => kv.Value);
|
||||
@ -151,27 +143,43 @@ public static class Renderer
|
||||
int pixelsX = (int)(lonDiff * scaleFactor);
|
||||
int pixelsY = (int)(latDiff * scaleFactor);
|
||||
|
||||
Image ret = renderOver ?? new Bitmap(pixelsX, pixelsY, PixelFormat.Format32bppPArgb);
|
||||
Graphics g = Graphics.FromImage(ret);
|
||||
if(renderOver is null)
|
||||
g.Clear(Color.White);
|
||||
|
||||
float pointSize = PenThickness * 1.5f;
|
||||
Renderer renderer;
|
||||
if(drawOver is null)
|
||||
switch (renderType)
|
||||
{
|
||||
case RenderType.svg:
|
||||
renderer = new SVGRenderer(pixelsX, pixelsY);
|
||||
break;
|
||||
default:
|
||||
renderer = new PNGRenderer(pixelsX, pixelsY);
|
||||
break;
|
||||
}
|
||||
else
|
||||
renderer = drawOver;
|
||||
|
||||
foreach (KeyValuePair<OsmNode, double> kv in gScore)
|
||||
{
|
||||
double percentage = (kv.Value - minWeight) / (maxWeight - minWeight);
|
||||
Brush b = new SolidBrush(ColorInterp(WeightStartColor, WeightEndColor, percentage));
|
||||
|
||||
float x = (kv.Key.coordinates.longitude - minLon) * scaleFactor;
|
||||
float y = (maxLat - kv.Key.coordinates.latitude) * scaleFactor;
|
||||
|
||||
x -= pointSize / 2;
|
||||
y -= pointSize / 2;
|
||||
g.FillEllipse(b, x, y, pointSize, pointSize);
|
||||
renderer.DrawDot(x, y, PenThickness, ColorInterp(WeightStartColor, WeightEndColor, percentage));
|
||||
}
|
||||
|
||||
return new ValueTuple<Image, Bounds>(ret, new Bounds(minLat,minLon,maxLat,maxLon));
|
||||
renderer.bounds = new Bounds(minLat,minLon,maxLat,maxLon);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public static Renderer DrawPathfinder(Pathfinder pathfinder, RenderType renderType)
|
||||
{
|
||||
Console.WriteLine("Rendering loaded Regions");
|
||||
Renderer areaRender = DrawArea(pathfinder.regionManager, renderType);
|
||||
Console.WriteLine("Rendering gScores (Weights)");
|
||||
Renderer areaGScoreRender = DrawGScores(pathfinder.gScore!, renderType, areaRender);
|
||||
Console.WriteLine("Rendering path");
|
||||
Renderer areaGScorePathRender = DrawPath(pathfinder.pathResult!, renderType, areaGScoreRender);
|
||||
|
||||
return areaGScorePathRender;
|
||||
}
|
||||
|
||||
/*
|
||||
|
92
RenderPath/SVGRenderer.cs
Normal file
92
RenderPath/SVGRenderer.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace RenderPath;
|
||||
using System.Xml;
|
||||
|
||||
public class SVGRenderer : Renderer
|
||||
{
|
||||
private readonly XmlDocument _image;
|
||||
private XmlElement _document;
|
||||
|
||||
public SVGRenderer(int width, int height)
|
||||
{
|
||||
_image = new XmlDocument();
|
||||
CreateTree(width, height);
|
||||
}
|
||||
|
||||
public SVGRenderer(XmlDocument renderOver)
|
||||
{
|
||||
_image = renderOver;
|
||||
_document = _image.GetElementById("svg")!;
|
||||
}
|
||||
|
||||
private void CreateTree(int width, int height)
|
||||
{
|
||||
XmlDeclaration xmlDeclaration = _image.CreateXmlDeclaration( "1.0", "UTF-8", null );
|
||||
_image.InsertBefore(xmlDeclaration, _image.DocumentElement);
|
||||
XmlElement pElement = _image.CreateElement("svg");
|
||||
XmlAttribute xmlns = _image.CreateAttribute("xmlns");
|
||||
xmlns.Value = "http://www.w3.org/2000/svg";
|
||||
pElement.Attributes.Append(xmlns);
|
||||
XmlAttribute aWidth = _image.CreateAttribute("width");
|
||||
aWidth.Value = width.ToString();
|
||||
pElement.Attributes.Append(aWidth);
|
||||
XmlAttribute aHeight = _image.CreateAttribute("height");
|
||||
aHeight.Value = height.ToString();
|
||||
pElement.Attributes.Append(aHeight);
|
||||
_image.AppendChild(pElement);
|
||||
_document = pElement;
|
||||
}
|
||||
|
||||
public override void DrawLine(float x1, float y1, float x2, float y2, int width, Color color)
|
||||
{
|
||||
XmlElement newLine = _image.CreateElement("line");
|
||||
XmlAttribute aX1 = _image.CreateAttribute("x1");
|
||||
aX1.Value = Math.Floor(x1).ToString("0");
|
||||
newLine.Attributes.Append(aX1);
|
||||
XmlAttribute aY1 = _image.CreateAttribute("y1");
|
||||
aY1.Value = Math.Floor(y1).ToString("0");
|
||||
newLine.Attributes.Append(aY1);
|
||||
XmlAttribute aX2 = _image.CreateAttribute("x2");
|
||||
aX2.Value = Math.Floor(x2).ToString("0");
|
||||
newLine.Attributes.Append(aX2);
|
||||
XmlAttribute aY2 = _image.CreateAttribute("y2");
|
||||
aY2.Value = Math.Floor(y2).ToString("0");
|
||||
newLine.Attributes.Append(aY2);
|
||||
XmlAttribute stroke = _image.CreateAttribute("stroke-width");
|
||||
stroke.Value = width.ToString();
|
||||
newLine.Attributes.Append(stroke);
|
||||
XmlAttribute aColor = _image.CreateAttribute("stroke");
|
||||
aColor.Value = HexFromColor(color);
|
||||
newLine.Attributes.Append(aColor);
|
||||
_document.AppendChild(newLine);
|
||||
}
|
||||
|
||||
public override void DrawDot(float x, float y, int radius, Color color)
|
||||
{
|
||||
XmlElement newCircle = _image.CreateElement("circle");
|
||||
XmlAttribute aX = _image.CreateAttribute("cx");
|
||||
aX.Value = Math.Floor(x).ToString("0");
|
||||
newCircle.Attributes.Append(aX);
|
||||
XmlAttribute aY = _image.CreateAttribute("cy");
|
||||
aY.Value = Math.Floor(y).ToString("0");
|
||||
newCircle.Attributes.Append(aY);
|
||||
XmlAttribute aR = _image.CreateAttribute("r");
|
||||
aR.Value = radius.ToString();
|
||||
newCircle.Attributes.Append(aR);
|
||||
XmlAttribute fill = _image.CreateAttribute("fill");
|
||||
fill.Value = HexFromColor(color);
|
||||
newCircle.Attributes.Append(fill);
|
||||
_document.AppendChild(newCircle);
|
||||
}
|
||||
|
||||
public override void Save(string path)
|
||||
{
|
||||
_image.Save($"{path}.svg");
|
||||
}
|
||||
|
||||
private static string HexFromColor(Color color)
|
||||
{
|
||||
return $"#{color.R:X2}{color.G:X2}{color.B:X2}";
|
||||
}
|
||||
}
|
121
Server/Server.cs
121
Server/Server.cs
@ -1,5 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Text.Json;
|
||||
using OSMDatastructure;
|
||||
using OSMDatastructure.Graph;
|
||||
using Pathfinding;
|
||||
@ -11,6 +13,7 @@ namespace Server;
|
||||
public class Server
|
||||
{
|
||||
|
||||
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
ConsoleWriter newConsole = new();
|
||||
@ -26,27 +29,33 @@ public class Server
|
||||
Coordinates start = new (48.7933798f, 9.8275859f);
|
||||
Coordinates finish = new (48.795918f, 9.021618f);
|
||||
|
||||
TestVariables(workingDir, start, finish, 16);
|
||||
GetShortestRoute("D:");
|
||||
Pathfinder result = new Pathfinder(workingDir, 2, 30).AStar(start, finish, Tag.SpeedType.car, 1);
|
||||
Renderer image = Renderer.DrawPathfinder(result, Renderer.RenderType.png);
|
||||
image.Save("D:/stuttgart-regbez-latest");
|
||||
|
||||
/*
|
||||
ValueTuple<Image, Renderer.Bounds> area = RenderAreaBaseImage(workingDir, start, finish);
|
||||
area.Item1.Save(@"D:\Base.png", ImageFormat.Png);
|
||||
|
||||
ValueTuple<Image, Renderer.Bounds> areaDistance = Renderer.DrawPath(
|
||||
PathResult.PathresultFromFile(@"D:\angle0,140_level0,020_same0,160.result"), Image.FromFile(@"D:\Base.png"), area.Item2);
|
||||
areaDistance.Item1.Save(@"D:\Distance.png", ImageFormat.Png);
|
||||
|
||||
ValueTuple<Image, Renderer.Bounds> areaWeight = Renderer.DrawPath(
|
||||
PathResult.PathresultFromFile(@"D:\angle0,160_level0,020_same0,020.result"), Image.FromFile(@"D:\Base.png"), area.Item2);
|
||||
areaWeight.Item1.Save(@"D:\Weight.png", ImageFormat.Png);
|
||||
if(File.Exists(@"D:\bounds"))
|
||||
File.Delete(@"D:\bounds");
|
||||
RegionManager rm = LoadRegions(workingDir, start, finish);
|
||||
Renderer areaRender = Renderer.DrawArea(rm, Renderer.RenderType.PNG);
|
||||
FileStream s = new(@"D:\bounds", FileMode.OpenOrCreate);
|
||||
JsonSerializer.Serialize(s, areaRender.bounds, JsonSerializerOptions.Default);
|
||||
areaRender.Save(@"D:\Base");
|
||||
s.Dispose();
|
||||
*/
|
||||
|
||||
|
||||
|
||||
//TestVariables(workingDir, start, finish, 12);
|
||||
//GetShortestRoute("D:");
|
||||
|
||||
/*
|
||||
string parentFolder = new DirectoryInfo(workingDir).Parent!.FullName;
|
||||
Renderer.Bounds bounds = JsonSerializer.Deserialize<Renderer.Bounds>(new FileStream(@"D:\bounds", FileMode.Open));
|
||||
Image baseImage = Image.FromFile(@"D:\Base.png");
|
||||
|
||||
Pathfinder result = new Pathfinder(workingDir, 0.002, 0,
|
||||
0, 1, 30).AStar(start,
|
||||
finish, Tag.SpeedType.car);
|
||||
Pathfinder result = new Pathfinder(workingDir, 2, 30).AStar(start,
|
||||
finish, Tag.SpeedType.car, 4);
|
||||
|
||||
Console.WriteLine($"Calc-time {result.pathResult!.calcTime} Path-length: {result.pathResult.pathNodes.Count} Visited-nodes: {result.gScore!.Count}");
|
||||
|
||||
@ -56,41 +65,41 @@ public class Server
|
||||
result.SaveResult(Path.Join(parentFolder, resultFileName));
|
||||
|
||||
string renderFileName = $"{new DirectoryInfo(workingDir).Name}-{fileName}.render.png";
|
||||
Image render = Renderer.DrawPathfinder(result);
|
||||
#pragma warning disable CA1416
|
||||
|
||||
Image renderWeights = Renderer.DrawGScores(result.gScore, baseImage, bounds).Item1;
|
||||
Image render = Renderer.DrawPath(result.pathResult, renderWeights, bounds).Item1;
|
||||
render.Save(Path.Join(parentFolder, renderFileName), ImageFormat.Png);
|
||||
#pragma warning restore CA1416*/
|
||||
Console.Beep(400, 100);
|
||||
*/
|
||||
Console.Beep(400, 50);
|
||||
Console.Beep(600, 50);
|
||||
Console.Beep(400, 50);
|
||||
Console.Beep(600, 50);
|
||||
}
|
||||
|
||||
private static void GetShortestRoute(string directory)
|
||||
{
|
||||
DateTime start = DateTime.Now;
|
||||
HashSet<string> allFiles = Directory.GetFiles(directory).Where(file => file.EndsWith(".result")).ToHashSet();
|
||||
PathResult first = PathResult.PathresultFromFile(allFiles.First());
|
||||
KeyValuePair<PathResult, string> shortest = new(first, allFiles.First());
|
||||
KeyValuePair<PathResult, string> fastest = new(first, allFiles.First());
|
||||
KeyValuePair<PathResult, string> calcTime = new(first, allFiles.First());
|
||||
Dictionary<PathResult, string> results = new();
|
||||
int loaded = 0;
|
||||
foreach (string filePath in allFiles)
|
||||
{
|
||||
PathResult result = PathResult.PathresultFromFile(filePath);
|
||||
results.Add(result, filePath);
|
||||
Console.WriteLine($"{loaded++}/{allFiles.Count()} {filePath} " +
|
||||
$"Time elapsed: {DateTime.Now - start} " +
|
||||
$"Remaining {((DateTime.Now - start)/loaded)*(allFiles.Count-loaded)}");
|
||||
if (shortest.Key.distance > result.distance)
|
||||
shortest = new KeyValuePair<PathResult, string>(result, filePath);
|
||||
|
||||
if (fastest.Key.weight > result.weight)
|
||||
fastest = new KeyValuePair<PathResult, string>(result, filePath);
|
||||
|
||||
if (calcTime.Key.calcTime > result.calcTime)
|
||||
calcTime = new KeyValuePair<PathResult, string>(result, filePath);
|
||||
}
|
||||
Console.WriteLine($"Shortest: {shortest.Key.distance} {shortest.Value}\nFastest: {shortest.Key.weight} {fastest.Value}\nCalcTime: {calcTime.Key.calcTime} {calcTime.Value}");
|
||||
}
|
||||
|
||||
private static ValueTuple<Image, Renderer.Bounds> RenderAreaBaseImage(string workingDir, Coordinates c1, Coordinates c2)
|
||||
KeyValuePair<PathResult, string> shortest = results.MinBy(result => result.Key.distance);
|
||||
KeyValuePair<PathResult, string> fastest = results.MinBy(result => result.Key.weight);
|
||||
KeyValuePair<PathResult, string> calcTime = results.MinBy(result => result.Key.calcTime);
|
||||
Console.WriteLine($"\nShortest:\t{shortest.Key.distance:0.0} {shortest.Key.weight:0.00} {shortest.Key.calcTime} {shortest.Value}\n" +
|
||||
$"Fastest:\t{fastest.Key.distance:0.0} {fastest.Key.weight:0.00} {fastest.Key.calcTime} {fastest.Value}\n" +
|
||||
$"CalcTime:\t{calcTime.Key.distance:0.0} {calcTime.Key.weight:0.00} {calcTime.Key.calcTime} {calcTime.Value}");
|
||||
}
|
||||
|
||||
private static RegionManager LoadRegions(string workingDir, Coordinates c1, Coordinates c2)
|
||||
{
|
||||
float minLat = c1.latitude < c2.latitude ? c1.latitude : c2.latitude;
|
||||
float minLon = c1.longitude < c2.longitude ? c1.longitude : c2.longitude;
|
||||
@ -98,38 +107,51 @@ public class Server
|
||||
float maxLon = c1.longitude > c2.longitude ? c1.longitude : c2.longitude;
|
||||
|
||||
RegionManager allRegions = new(workingDir);
|
||||
for (float lat = minLat - Region.RegionSize; lat < maxLat + Region.RegionSize; lat += Region.RegionSize / 2)
|
||||
for (float lat = minLat - Region.RegionSize * 3; lat < maxLat + Region.RegionSize * 3; lat += Region.RegionSize / 2)
|
||||
{
|
||||
for (float lon = minLon - Region.RegionSize; lon < maxLon + Region.RegionSize; lon += Region.RegionSize / 2)
|
||||
{
|
||||
allRegions.GetRegion(new Coordinates(lat, lon));
|
||||
}
|
||||
}
|
||||
Console.WriteLine("Regions Loaded. Rendering.");
|
||||
ValueTuple<Image, Renderer.Bounds> baseRender = Renderer.DrawArea(allRegions);
|
||||
return baseRender;
|
||||
Console.WriteLine("Loaded needed Regions");
|
||||
return allRegions;
|
||||
}
|
||||
|
||||
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
|
||||
private static void TestVariables(string workingDir, Coordinates start, Coordinates finish, int threads)
|
||||
{
|
||||
string parentFolder = new DirectoryInfo(workingDir).Parent!.FullName;
|
||||
|
||||
RegionManager rm = new (workingDir);
|
||||
RegionManager rm = LoadRegions(workingDir, start, finish);
|
||||
|
||||
Queue<Thread> calcThreads = new();
|
||||
|
||||
for (double roadLevelPriority = 0.02; roadLevelPriority > 0; roadLevelPriority -= 0.001)
|
||||
Bounds bounds = JsonSerializer.Deserialize<Bounds>(new FileStream(@"D:\bounds", FileMode.Open))!;
|
||||
|
||||
|
||||
for (double extraTime = 1.5; extraTime >= 1; extraTime -= 0.25)
|
||||
{
|
||||
for (double maxAngle = 5; maxAngle < 45; maxAngle += 5)
|
||||
for (double roadFactor = 0.05; roadFactor < 5; roadFactor += 0.05)
|
||||
{
|
||||
double priority = roadLevelPriority;
|
||||
double angle = maxAngle;
|
||||
double road = roadFactor;
|
||||
double time = extraTime;
|
||||
calcThreads.Enqueue(new Thread(() =>
|
||||
{
|
||||
Pathfinder testresult = new Pathfinder(rm, priority, angle).AStar(start,
|
||||
finish, Tag.SpeedType.car);
|
||||
string fileName = $"angle{angle:0.000}_level{priority:0.000}.result";
|
||||
testresult.SaveResult(Path.Join(parentFolder, fileName));
|
||||
Pathfinder testresult = new Pathfinder(workingDir, road, 30).AStar(start,
|
||||
finish, Tag.SpeedType.car, time);
|
||||
Image baseImage = Image.FromStream(new FileStream(@"D:\Base.png", FileMode.Open, FileAccess.Read, FileShare.Read,
|
||||
(int)new FileInfo(@"D:\Base.png").Length, FileOptions.Asynchronous));
|
||||
Renderer renderer = new PNGRenderer(baseImage);
|
||||
renderer.bounds = bounds;
|
||||
Renderer renderWeights = Renderer.DrawGScores(testresult.gScore!, Renderer.RenderType.png, renderer);
|
||||
Renderer render = Renderer.DrawPath(testresult.pathResult!, Renderer.RenderType.png, renderWeights);
|
||||
string fileName = $"road{road:0.00}_time{time:0.00}";
|
||||
string resultFileName = Path.Combine("D:", $"{fileName}.result");
|
||||
testresult.SaveResult(resultFileName);
|
||||
string imageFileName = Path.Combine("D:", fileName);
|
||||
render.Save(imageFileName);
|
||||
Console.WriteLine($"Saved {fileName}");
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -139,7 +161,8 @@ public class Server
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
HashSet<Thread> runningThreads = new();
|
||||
while (calcThreads.Count > 0)
|
||||
Console.WriteLine($"Running {threads} Threads on {totalTasks} Tasks.");
|
||||
while (calcThreads.Count > 0 || runningThreads.Count > 0)
|
||||
{
|
||||
while (runningThreads.Count < threads && calcThreads.Count > 0)
|
||||
{
|
||||
@ -153,7 +176,7 @@ public class Server
|
||||
if (newCompletedTasks > 0)
|
||||
{
|
||||
TimeSpan elapsedTime = DateTime.Now - startTime;
|
||||
Console.WriteLine($"To calculate: {calcThreads.Count}/{totalTasks} Time Average: {(elapsedTime)/(completedTasks)} Elapsed: {elapsedTime} Remaining: {(elapsedTime)/(completedTasks)*calcThreads.Count}");
|
||||
Console.WriteLine($"To calculate: {calcThreads.Count}(+{runningThreads.Count} running)/{totalTasks} Time Average: {(elapsedTime/completedTasks)} Elapsed: {elapsedTime} Remaining: {(elapsedTime/completedTasks*calcThreads.Count)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user