Compare commits
198 Commits
9444e2ac8e
...
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 | |||
ec6725a5c5 | |||
886ccaa8dc | |||
af1d9baf4f | |||
e2332847cd | |||
dca4d56866 | |||
5f6cccd17d | |||
97a8c2ea6f | |||
b89a3715a1 | |||
6bc1d3c7ce | |||
5b8a1d1e10 | |||
7856f1c66c | |||
97a057a3d4 | |||
bc39785f6f | |||
18822e2152 | |||
68cb0ee3fd | |||
465d40a475 | |||
7fd9047ac4 | |||
601200a8d6 | |||
a758c8c63e | |||
ed46a419e3 | |||
a1d9ccad46 | |||
914731c8a3 | |||
d8ce6e4ce5 | |||
976108569b | |||
6373874495 | |||
c43c6dc985 | |||
33232a7eb7 | |||
cf5b1e9945 | |||
aa8b1e4451 | |||
95c0088b73 | |||
cd3905915b | |||
dd37430761 | |||
42e915ee05 | |||
7d769a064f | |||
93a448e189 | |||
750ba5c624 | |||
1facca84ba | |||
8b7cfcbd77 | |||
28ab2b2bb8 | |||
7201b9c993 | |||
d1f311a76b | |||
2b252e2b06 | |||
d456275fc1 | |||
2bd6c5d9c4 | |||
90a09e84c5 | |||
b87d8a0300 | |||
23429c8a00 | |||
946fa0206b | |||
5a1dce9883 | |||
619cad61ee | |||
bfb117164e | |||
208c000577 | |||
5212e43897 | |||
6fb88b5c9c | |||
9e0c4f65db | |||
055a751c9d | |||
08ebc9a26b | |||
d35aab9c39 | |||
08e3da6fe3 | |||
367e9cfde0 | |||
a70284aa87 | |||
13915c9773 | |||
c373451007 | |||
308579279b | |||
2131ac4afe | |||
428fcb9bf8 | |||
6b496957d7 | |||
874d60992e | |||
c1557b7678 | |||
14533c150f | |||
5289020d44 | |||
bb789e731d | |||
9e72d50448 | |||
c238a9eed3 | |||
932465a564 | |||
a0d2284e45 | |||
6938c86ce2 | |||
6e836db79b | |||
9448187452 | |||
2ca4207fd7 | |||
0f53ae579c | |||
d8f8a41dcc | |||
6eab23ff16 | |||
206f9c5811 | |||
e0bb3ce3de | |||
2904be84f0 | |||
13beaeaf73 | |||
ea7ce1f630 | |||
9c7fec1c37 | |||
5efec08bbc | |||
05ae0bff6e | |||
8bd0c5a4d4 | |||
feb9b70e50 | |||
9ef0e421bc | |||
a54b189b08 | |||
585a9213ce | |||
bf08f38a1e | |||
fc5d388ecd | |||
58d1031524 | |||
fe0ccd0fca | |||
dfc9ffeb2c | |||
9c53c67763 | |||
f266c6c7e6 | |||
8813023cd6 | |||
72b5511c26 | |||
f42e458048 | |||
3e23635cd1 | |||
ed8558049c | |||
20d4da9e6f | |||
e9f1ba2e73 | |||
5fe0acf0b4 | |||
29189c5d8d | |||
7f06a6f880 | |||
8dd63411ea | |||
8f9f6f630e | |||
2c5ab070a2 | |||
4600105b0b | |||
0f0f4182ac | |||
5ebe843048 | |||
03322ea143 | |||
349ed9da94 | |||
87e260562f | |||
82d2de1537 | |||
01deb02666 | |||
2826ff2502 | |||
8cf13efae3 | |||
b3da6936da | |||
312536f0c0 | |||
556d4b1ffb | |||
2d5ffabb5d | |||
0bfc120ede | |||
806dcf98c9 | |||
c2cadd678c | |||
15628d3544 | |||
452c5d3177 | |||
9dc282253d | |||
f0cd97fbc7 | |||
3fce0f990a | |||
102499891c | |||
9ef1d4a978 | |||
b12f959f48 | |||
e7119b22ae | |||
cd1584eee7 | |||
e2a50250b8 | |||
b621ff632b | |||
07ff9602b8 | |||
ac77708834 | |||
255d924dc4 | |||
0d4597c12c | |||
6a2ddf3277 | |||
090981fb99 | |||
ac1ac62a00 | |||
7560d59c25 | |||
cb0442d760 | |||
0e70c72562 | |||
d2ab990bfe | |||
d7469aa190 | |||
583fe3c18d | |||
556b0736f1 | |||
d3680565fc | |||
55b9e87b7e | |||
9d2be7d972 | |||
4f0d5d4f30 | |||
f15171a9f1 | |||
d18e4f5abf | |||
e57912c589 | |||
aceecee07e | |||
d7b084659a |
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@ obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
.idea/
|
||||
*.DotSettings*
|
||||
|
13
.idea/.idea.OSMServer/.idea/.gitignore
generated
vendored
13
.idea/.idea.OSMServer/.idea/.gitignore
generated
vendored
@ -1,13 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/projectSettingsUpdater.xml
|
||||
/contentModel.xml
|
||||
/modules.xml
|
||||
/.idea.OSMServer.iml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
8
.idea/.idea.OSMServer/.idea/indexLayout.xml
generated
8
.idea/.idea.OSMServer/.idea/indexLayout.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
6
.idea/.idea.OSMServer/.idea/vcs.xml
generated
6
.idea/.idea.OSMServer/.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
23
API/API.csproj
Normal file
23
API/API.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Controllers" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Pathfinding\Pathfinding.csproj" />
|
||||
<ProjectReference Include="..\RenderPath\RenderPath.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
52
API/Program.cs
Normal file
52
API/Program.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using OSMDatastructure;
|
||||
using OSMDatastructure.Graph;
|
||||
using Pathfinding;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
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, 3);
|
||||
return result.pathResult;
|
||||
}
|
||||
);
|
||||
|
||||
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, 3);
|
||||
return result.pathResult;
|
||||
}
|
||||
);
|
||||
|
||||
app.MapGet("/getClosestNode", (float lat, float lon) =>
|
||||
{
|
||||
RegionManager regionManager = new RegionManager("D:/stuttgart-regbez-latest");
|
||||
return regionManager.ClosestNodeToCoordinates(new Coordinates(lat, lon), Tag.SpeedType.any);
|
||||
});
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
|
41
API/Properties/launchSettings.json
Normal file
41
API/Properties/launchSettings.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:39952",
|
||||
"sslPort": 44326
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7095;http://localhost:5057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
API/appsettings.Development.json
Normal file
8
API/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
9
API/appsettings.json
Normal file
9
API/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
@ -1,271 +0,0 @@
|
||||
using System.Text;
|
||||
|
||||
namespace OSMDatastructure;
|
||||
|
||||
public static class ByteConverter
|
||||
{
|
||||
#region Region
|
||||
/*
|
||||
* | regionHash | Nodes |
|
||||
* |---------------+---------------+
|
||||
* | 8 bytes |
|
||||
* | ulong |
|
||||
*/
|
||||
public static byte[] GetBytes(Region region)
|
||||
{
|
||||
int totalNodeBytes = 0;
|
||||
HashSet<byte[]> nodes = new();
|
||||
foreach (OsmNode node in region.nodes)
|
||||
{
|
||||
byte[] nodeBytes = GetBytes(node);
|
||||
totalNodeBytes += nodeBytes.Length;
|
||||
nodes.Add(nodeBytes);
|
||||
}
|
||||
|
||||
byte[] retBytes = new byte[sizeof(ulong) + totalNodeBytes];
|
||||
Array.Copy(BitConverter.GetBytes(region.regionHash), 0, retBytes, 0, sizeof(ulong));
|
||||
int offset = sizeof(ulong);
|
||||
foreach (byte[] node in nodes)
|
||||
{
|
||||
Array.Copy(node, 0, retBytes, offset, node.Length);
|
||||
offset += node.Length;
|
||||
}
|
||||
|
||||
return retBytes;
|
||||
}
|
||||
|
||||
public static Region ToRegion(byte[] bytes)
|
||||
{
|
||||
Region retRegion = new Region(BitConverter.ToUInt64(bytes, 0));
|
||||
int offset = sizeof(ulong);
|
||||
while (offset < bytes.Length)
|
||||
{
|
||||
int size = BitConverter.ToInt32(bytes, offset);
|
||||
byte[] nodeBytes = new byte[size];
|
||||
Array.Copy(bytes, offset, nodeBytes, 0, size);
|
||||
offset += size;
|
||||
retRegion.nodes.Add(ToNode(nodeBytes));
|
||||
}
|
||||
|
||||
return retRegion;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coordinates
|
||||
|
||||
/*
|
||||
* | Latitude | Longitude |
|
||||
* |---------------+---------------+
|
||||
* | 4 bytes | 4 bytes |
|
||||
* | float | float |
|
||||
*/
|
||||
|
||||
public static byte[] GetBytes(Coordinates coordinates)
|
||||
{
|
||||
byte[] retBytes = new byte[8];
|
||||
Array.Copy(BitConverter.GetBytes(coordinates.latitude), 0, retBytes, 0, sizeof(float));
|
||||
Array.Copy(BitConverter.GetBytes(coordinates.longitude), 0, retBytes, sizeof(float), sizeof(float));
|
||||
return retBytes;
|
||||
}
|
||||
|
||||
public static Coordinates ToCoordinates(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length != 8)
|
||||
throw new ArgumentException("Needs exactly 8 bytes", nameof(bytes));
|
||||
float latitude = BitConverter.ToSingle(bytes, 0);
|
||||
float longitude = BitConverter.ToSingle(bytes, sizeof(float));
|
||||
return new Coordinates(latitude, longitude);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OsmNode
|
||||
|
||||
/*
|
||||
* | Size | Coordinates | Edges
|
||||
* |---------------+---------------+---------------+
|
||||
* | 4 bytes | 8 bytes |
|
||||
* | int | byte[] |
|
||||
*/
|
||||
|
||||
public static byte[] GetBytes(OsmNode node)
|
||||
{
|
||||
int totalBytes = sizeof(int) + 8; //size + Coordinates
|
||||
HashSet<byte[]> edges = new();
|
||||
foreach (OsmEdge edge in node.edges)
|
||||
{
|
||||
byte[] edgeBytes = GetBytes(edge);
|
||||
edges.Add(edgeBytes);
|
||||
totalBytes += edgeBytes.Length;
|
||||
}
|
||||
|
||||
byte[] retBytes = new byte[totalBytes];
|
||||
Array.Copy(BitConverter.GetBytes(totalBytes), retBytes, 4);
|
||||
Array.Copy(GetBytes(node.coordinates), 0, retBytes, 4, 8);
|
||||
int offset = 4 + 8;
|
||||
foreach (byte[] edgeBytes in edges)
|
||||
{
|
||||
Array.Copy(edgeBytes, 0, retBytes, offset, edgeBytes.Length);
|
||||
offset += edgeBytes.Length;
|
||||
}
|
||||
|
||||
return retBytes;
|
||||
}
|
||||
|
||||
public static OsmNode ToNode(byte[] bytes)
|
||||
{
|
||||
byte[] coordinateBytes = new byte[8];
|
||||
Array.Copy(bytes, 4, coordinateBytes, 0, 8);
|
||||
Coordinates coordinates = ToCoordinates(coordinateBytes);
|
||||
|
||||
OsmNode retNode = new OsmNode(coordinates);
|
||||
|
||||
int offset = sizeof(int) + 8;
|
||||
while (offset < bytes.Length)
|
||||
{
|
||||
int tagsSize = BitConverter.ToInt32(bytes, offset);
|
||||
byte[] edgeBytes = new byte[sizeof(int) + 8 + tagsSize];
|
||||
Array.Copy(bytes, offset, edgeBytes, 0, edgeBytes.Length);
|
||||
offset += edgeBytes.Length;
|
||||
retNode.edges.Add(ToEdge(edgeBytes));
|
||||
}
|
||||
|
||||
return retNode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OsmEdge
|
||||
|
||||
/* Edge:
|
||||
* | tagsSize | neighborCoord | tags
|
||||
* |---------------+---------------+--------------
|
||||
* | 4 bytes | 8 bytes |
|
||||
* | int | byte[] |
|
||||
*
|
||||
*/
|
||||
|
||||
public static byte[] GetBytes(OsmEdge edge)
|
||||
{
|
||||
int totalBytes = sizeof(int) + 8; //size + Coordinates
|
||||
int offset = totalBytes;
|
||||
|
||||
int tagsSize = 0;
|
||||
HashSet<byte[]> tags = new();
|
||||
foreach (KeyValuePair<OsmEdge.tagType, object> kv in edge.tags)
|
||||
{
|
||||
byte[] tagBytes = GetBytes(kv);
|
||||
tags.Add(tagBytes);
|
||||
tagsSize += tagBytes.Length;
|
||||
}
|
||||
|
||||
totalBytes += tagsSize;
|
||||
byte[] retBytes = new byte[totalBytes];
|
||||
Array.Copy(BitConverter.GetBytes(tagsSize), retBytes, 4);
|
||||
Array.Copy(GetBytes(edge.neighborCoordinates), 0, retBytes, sizeof(int), 8);
|
||||
|
||||
foreach (byte[] tag in tags)
|
||||
{
|
||||
Array.Copy(tag, 0, retBytes, offset, tag.Length);
|
||||
offset += tag.Length;
|
||||
}
|
||||
|
||||
return retBytes;
|
||||
}
|
||||
|
||||
public static OsmEdge ToEdge(byte[] bytes)
|
||||
{
|
||||
byte[] coordinateBytes = new byte[8];
|
||||
Array.Copy(bytes, 4, coordinateBytes, 0, 8);
|
||||
Coordinates coordinates = ToCoordinates(coordinateBytes);
|
||||
|
||||
OsmEdge retEdge = new OsmEdge(coordinates);
|
||||
|
||||
int offset = sizeof(int) + 8;
|
||||
while (offset < bytes.Length)
|
||||
{
|
||||
int tagContentSize = BitConverter.ToInt32(bytes, offset);
|
||||
byte[] tagBytes = new byte[sizeof(int) + 1 + tagContentSize];
|
||||
Array.Copy(bytes, offset, tagBytes, 0, tagBytes.Length);
|
||||
offset += tagBytes.Length;
|
||||
KeyValuePair<OsmEdge.tagType, object> tag = ToTag(tagBytes);
|
||||
retEdge.tags.Add(tag.Key, tag.Value);
|
||||
}
|
||||
|
||||
return retEdge;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OsmEdge.Tag
|
||||
/*
|
||||
* Tag:
|
||||
* | contentSize | tagType | tagContent
|
||||
* |---------------+---------------+---------------
|
||||
* | 4 bytes | 1 byte |
|
||||
* | int | byte |
|
||||
*/
|
||||
|
||||
public static byte[] GetBytes(KeyValuePair<OsmEdge.tagType, object> tag)
|
||||
{
|
||||
byte[] objectBytes;
|
||||
Type objectType = tag.Value.GetType();
|
||||
if (objectType == Type.GetType("System.String"))
|
||||
{
|
||||
objectBytes = Encoding.ASCII.GetBytes((string)tag.Value);
|
||||
}
|
||||
else if (objectType == Type.GetType("System.Boolean"))
|
||||
{
|
||||
objectBytes = BitConverter.GetBytes((bool)tag.Value);
|
||||
}
|
||||
else if (objectType == Type.GetType("System.Int32"))
|
||||
{
|
||||
objectBytes = BitConverter.GetBytes((int)tag.Value);
|
||||
}
|
||||
else if (objectType == Type.GetType("System.UInt64"))
|
||||
{
|
||||
objectBytes = BitConverter.GetBytes((ulong)tag.Value);
|
||||
}
|
||||
else if (objectType == Type.GetType("System.Byte") || objectType.Name == "wayType")
|
||||
{
|
||||
objectBytes = new[] { (byte)tag.Value };
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(string.Format("Tag-Bytes object-Type: {0}", tag.Value.GetType()));
|
||||
}
|
||||
|
||||
byte[] retBytes = new byte[sizeof(int) + 1 + objectBytes.Length];
|
||||
Array.Copy(BitConverter.GetBytes(objectBytes.Length), 0, retBytes, 0, sizeof(int));
|
||||
retBytes[sizeof(int)] = (byte)tag.Key;
|
||||
Array.Copy(objectBytes, 0, retBytes, sizeof(int) + 1, objectBytes.Length);
|
||||
return retBytes;
|
||||
}
|
||||
|
||||
public static KeyValuePair<OsmEdge.tagType, object> ToTag(byte[] bytes)
|
||||
{
|
||||
OsmEdge.tagType tagType = (OsmEdge.tagType)bytes[sizeof(int)];
|
||||
int contentSize = BitConverter.ToInt32(bytes, 0);
|
||||
byte[] content = new byte[contentSize];
|
||||
Array.Copy(bytes, sizeof(int) + 1, content, 0, contentSize);
|
||||
|
||||
switch (tagType)
|
||||
{
|
||||
case OsmEdge.tagType.highway:
|
||||
return new KeyValuePair<OsmEdge.tagType, object>(tagType, (OsmEdge.wayType)content[0]);
|
||||
case OsmEdge.tagType.maxspeed:
|
||||
return new KeyValuePair<OsmEdge.tagType, object>(tagType, content[0]);
|
||||
case OsmEdge.tagType.oneway:
|
||||
return new KeyValuePair<OsmEdge.tagType, object>(tagType, BitConverter.ToBoolean(content));
|
||||
case OsmEdge.tagType.forward:
|
||||
return new KeyValuePair<OsmEdge.tagType, object>(tagType, BitConverter.ToBoolean(content));
|
||||
case OsmEdge.tagType.id:
|
||||
return new KeyValuePair<OsmEdge.tagType, object>(tagType, BitConverter.ToUInt64(content));
|
||||
default:
|
||||
return new KeyValuePair<OsmEdge.tagType, object>(tagType, content);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
namespace OSMDatastructure;
|
||||
|
||||
public class Coordinates
|
||||
{
|
||||
public float latitude { get; }
|
||||
public float longitude { get; }
|
||||
|
||||
public Coordinates(float latitude, float longitude)
|
||||
{
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private const float decimalCoordsSave = 10000;
|
||||
private const ulong offset = 10000000;
|
||||
//Latitude maxChars = 7
|
||||
//Longitude maxChars = 8
|
||||
public ulong GetHash()
|
||||
{
|
||||
ulong latHash = Convert.ToUInt64((this.latitude + 90) * decimalCoordsSave);
|
||||
ulong lonHash = Convert.ToUInt64((this.longitude + 180) * decimalCoordsSave);
|
||||
return latHash * offset + lonHash;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(latitude, longitude);
|
||||
}
|
||||
|
||||
public ulong GetRegionHash()
|
||||
{
|
||||
float latRegion = this.latitude - this.latitude % Region.regionSize;
|
||||
float lonRegion = this.longitude - this.longitude % Region.regionSize;
|
||||
ulong latHash = Convert.ToUInt64((latRegion + 90) * decimalCoordsSave);
|
||||
ulong lonHash = Convert.ToUInt64((lonRegion + 180) * decimalCoordsSave);
|
||||
return latHash * offset + lonHash;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("COORDINATES Lat: {0} Lon: {1}", this.latitude, this.longitude);
|
||||
}
|
||||
}
|
53
OSMDatastructure/Graph/Coordinates.cs
Normal file
53
OSMDatastructure/Graph/Coordinates.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace OSMDatastructure.Graph;
|
||||
|
||||
[Serializable]
|
||||
public class Coordinates
|
||||
{
|
||||
public float latitude { get; }
|
||||
public float longitude { get; }
|
||||
|
||||
[JsonConstructor]
|
||||
public Coordinates(float latitude, float longitude)
|
||||
{
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj == null || obj.GetType() != this.GetType())
|
||||
return false;
|
||||
Coordinates convObj = (Coordinates)obj;
|
||||
return convObj.latitude.Equals(this.latitude) && convObj.longitude.Equals(longitude);
|
||||
}
|
||||
|
||||
public static ulong GetRegionHashCode(float latitude, float longitude)
|
||||
{
|
||||
float latRegion = latitude - latitude % Region.RegionSize;
|
||||
float lonRegion = longitude - longitude % Region.RegionSize;
|
||||
return GetHashCode(latRegion, lonRegion);
|
||||
}
|
||||
|
||||
public static ulong GetRegionHashCode(Coordinates coordinates)
|
||||
{
|
||||
return GetRegionHashCode(coordinates.latitude, coordinates.longitude);
|
||||
}
|
||||
|
||||
private const float decimalCoordsSave = 10000; //Latitude maxChars = 7
|
||||
private const ulong offset = 10000000; //Longitude maxChars = 8
|
||||
public static ulong GetHashCode(float latitude, float longitude)
|
||||
{
|
||||
ulong latHash = Convert.ToUInt64((latitude + 90) * decimalCoordsSave);
|
||||
ulong lonHash = Convert.ToUInt64((longitude + 180) * decimalCoordsSave);
|
||||
return latHash * offset + lonHash;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"Coordinates lat:{latitude.ToString(NumberFormatInfo.InvariantInfo)} lon:{longitude.ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
27
OSMDatastructure/Graph/OsmEdge.cs
Normal file
27
OSMDatastructure/Graph/OsmEdge.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace OSMDatastructure.Graph;
|
||||
|
||||
[Serializable]
|
||||
public class OsmEdge
|
||||
{
|
||||
public ulong wayId { get; }
|
||||
public ulong startId { get; }
|
||||
public ulong neighborId { get; }
|
||||
public ulong neighborRegion { get; }
|
||||
|
||||
|
||||
[JsonConstructor]
|
||||
public OsmEdge(ulong wayId, ulong startId, ulong neighborId, ulong neighborRegion)
|
||||
{
|
||||
this.wayId = wayId;
|
||||
this.startId = startId;
|
||||
this.neighborId = neighborId;
|
||||
this.neighborRegion = neighborRegion;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Edge wayId:{wayId} n1:{startId} n2:{neighborId} in regionId:{neighborRegion}";
|
||||
}
|
||||
}
|
45
OSMDatastructure/Graph/OsmNode.cs
Normal file
45
OSMDatastructure/Graph/OsmNode.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace OSMDatastructure.Graph;
|
||||
|
||||
[Serializable]
|
||||
public class OsmNode
|
||||
{
|
||||
public ulong nodeId { get; }
|
||||
public HashSet<OsmEdge> edges { get; set; }
|
||||
public Coordinates coordinates { get; }
|
||||
|
||||
public OsmNode(ulong nodeId, float lat, float lon)
|
||||
{
|
||||
this.nodeId = nodeId;
|
||||
edges = new();
|
||||
coordinates = new Coordinates(lat, lon);
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public OsmNode(ulong nodeId, Coordinates coordinates)
|
||||
{
|
||||
this.nodeId = nodeId;
|
||||
edges = new();
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
public OsmEdge? GetEdgeToNode(OsmNode n)
|
||||
{
|
||||
HashSet<OsmEdge> e = edges.Where(edge => edge.neighborId == n.nodeId).ToHashSet();
|
||||
if (e.Count > 0)
|
||||
return e.First();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj != null && obj.GetType() == this.GetType() && ((OsmNode)obj).nodeId == this.nodeId;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Node id:{nodeId} coordinates:{coordinates} edges-count:{edges.Count}";
|
||||
}
|
||||
}
|
@ -6,4 +6,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.Serialization.Json" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,196 +0,0 @@
|
||||
namespace OSMDatastructure;
|
||||
|
||||
public class OsmEdge
|
||||
{
|
||||
public Coordinates neighborCoordinates { get; }
|
||||
public readonly Dictionary<tagType, object> tags = new();
|
||||
|
||||
public OsmEdge(float lat, float lon)
|
||||
{
|
||||
this.neighborCoordinates = new Coordinates(lat, lon);
|
||||
this.tags = new();
|
||||
}
|
||||
|
||||
public OsmEdge(Coordinates neighborCoordinates)
|
||||
{
|
||||
this.neighborCoordinates = neighborCoordinates;
|
||||
this.tags = new();
|
||||
}
|
||||
|
||||
public OsmEdge(float lat, float lon, Dictionary<tagType, object> tags)
|
||||
{
|
||||
this.neighborCoordinates = new Coordinates(lat, lon);
|
||||
//To prevent "EMPTY" tags
|
||||
foreach (KeyValuePair<tagType, object> tag in tags)
|
||||
{
|
||||
if(tag.Key != tagType.EMPTY)
|
||||
this.tags.Add(tag.Key, tag.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public OsmEdge(Coordinates neighborCoordinates, Dictionary<tagType, object> tags)
|
||||
{
|
||||
this.neighborCoordinates = neighborCoordinates;
|
||||
//To prevent "EMPTY" tags
|
||||
foreach (KeyValuePair<tagType, object> tag in tags)
|
||||
{
|
||||
if(tag.Key != tagType.EMPTY)
|
||||
this.tags.Add(tag.Key, tag.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public enum tagType : byte
|
||||
{
|
||||
highway, oneway, footway, sidewalk, cycleway, busway, forward, maxspeed, name, surface, lanes, access, tracktype, id, EMPTY
|
||||
}
|
||||
|
||||
public static Dictionary<wayType, byte> speedcar = new() {
|
||||
{ wayType.NONE, 0 },
|
||||
{ wayType.motorway, 110 },
|
||||
{ wayType.trunk, 100 },
|
||||
{ wayType.primary, 80 },
|
||||
{ wayType.secondary, 80 },
|
||||
{ wayType.tertiary, 70 },
|
||||
{ wayType.unclassified, 20 },
|
||||
{ wayType.residential, 10 },
|
||||
{ wayType.motorway_link, 50 },
|
||||
{ wayType.trunk_link, 50 },
|
||||
{ wayType.primary_link, 30 },
|
||||
{ wayType.secondary_link, 25 },
|
||||
{ wayType.tertiary_link, 25 },
|
||||
{ wayType.living_street, 10 },
|
||||
{ wayType.service, 0 },
|
||||
{ wayType.pedestrian, 0 },
|
||||
{ wayType.track, 0 },
|
||||
{ wayType.bus_guideway, 0 },
|
||||
{ wayType.escape, 0 },
|
||||
{ wayType.raceway, 0 },
|
||||
{ wayType.road, 25 },
|
||||
{ wayType.busway, 0 },
|
||||
{ wayType.footway, 0 },
|
||||
{ wayType.bridleway, 0 },
|
||||
{ wayType.steps, 0 },
|
||||
{ wayType.corridor, 0 },
|
||||
{ wayType.path, 0 },
|
||||
{ wayType.cycleway, 0 },
|
||||
{ wayType.construction, 0 }
|
||||
};
|
||||
|
||||
public static Dictionary<wayType, byte> speedped = new() {
|
||||
{ wayType.NONE, 0 },
|
||||
{ wayType.motorway, 0 },
|
||||
{ wayType.trunk, 0 },
|
||||
{ wayType.primary, 0 },
|
||||
{ wayType.secondary, 0 },
|
||||
{ wayType.tertiary, 0 },
|
||||
{ wayType.unclassified, 1 },
|
||||
{ wayType.residential, 3 },
|
||||
{ wayType.motorway_link, 0 },
|
||||
{ wayType.trunk_link, 0 },
|
||||
{ wayType.primary_link, 0 },
|
||||
{ wayType.secondary_link, 0 },
|
||||
{ wayType.tertiary_link, 0 },
|
||||
{ wayType.living_street, 5 },
|
||||
{ wayType.service, 2 },
|
||||
{ wayType.pedestrian, 5 },
|
||||
{ wayType.track, 0 },
|
||||
{ wayType.bus_guideway, 0 },
|
||||
{ wayType.escape, 0 },
|
||||
{ wayType.raceway, 0 },
|
||||
{ wayType.road, 3 },
|
||||
{ wayType.busway, 0 },
|
||||
{ wayType.footway, 4 },
|
||||
{ wayType.bridleway, 1 },
|
||||
{ wayType.steps, 2 },
|
||||
{ wayType.corridor, 3 },
|
||||
{ wayType.path, 4 },
|
||||
{ wayType.cycleway, 2 },
|
||||
{ wayType.construction, 0 }
|
||||
};
|
||||
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 }
|
||||
|
||||
public enum speedType { pedestrian, car, road }
|
||||
|
||||
public void AddTag(string key, string value)
|
||||
{
|
||||
KeyValuePair<tagType, object> tag = ConvertToTag(key, value);
|
||||
if(tag.Key != tagType.EMPTY)
|
||||
this.tags.Add(tag.Key, tag.Value);
|
||||
}
|
||||
|
||||
public static KeyValuePair<tagType, object> ConvertToTag(string key, string value)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case "highway":
|
||||
try
|
||||
{
|
||||
return new KeyValuePair<tagType, object>(tagType.highway, (wayType)Enum.Parse(typeof(wayType), value, true));
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return new KeyValuePair<tagType, object>(tagType.highway, wayType.unclassified);
|
||||
}
|
||||
case "maxspeed":
|
||||
try
|
||||
{
|
||||
return new KeyValuePair<tagType, object>(tagType.maxspeed, Convert.ToByte(value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//Console.WriteLine(e);
|
||||
//Console.WriteLine("Continuing...");
|
||||
return new KeyValuePair<tagType, object>(tagType.maxspeed, byte.MaxValue);
|
||||
}
|
||||
case "oneway":
|
||||
switch (value)
|
||||
{
|
||||
case "yes":
|
||||
return new KeyValuePair<tagType, object>(tagType.oneway, true);
|
||||
case "-1":
|
||||
return new KeyValuePair<tagType, object>(tagType.forward, false);
|
||||
case "no":
|
||||
return new KeyValuePair<tagType, object>(tagType.oneway, false);
|
||||
}
|
||||
break;
|
||||
case "id":
|
||||
return new KeyValuePair<tagType, object>(tagType.id, Convert.ToUInt64(value));
|
||||
}
|
||||
return new KeyValuePair<tagType, object>(tagType.EMPTY, 0);
|
||||
}
|
||||
|
||||
public ulong GetId()
|
||||
{
|
||||
return this.tags.ContainsKey(tagType.id) ? (ulong)this.tags[tagType.id] : 0;
|
||||
}
|
||||
|
||||
public wayType GetHighwayType()
|
||||
{
|
||||
return this.tags.ContainsKey(tagType.highway) ? (wayType)this.tags[tagType.highway] : wayType.NONE;
|
||||
}
|
||||
|
||||
public bool IsOneWay()
|
||||
{
|
||||
return this.tags.ContainsKey(tagType.oneway) && (bool)this.tags[tagType.oneway];
|
||||
}
|
||||
|
||||
public byte? GetMaxSpeed(speedType type)
|
||||
{
|
||||
if(type == speedType.road)
|
||||
{
|
||||
return this.tags.ContainsKey(tagType.maxspeed) ? (byte)this.tags[tagType.maxspeed] : null;
|
||||
}
|
||||
if(type == speedType.car)
|
||||
{
|
||||
return this.tags.ContainsKey(tagType.maxspeed)
|
||||
? (byte)this.tags[tagType.maxspeed]
|
||||
: speedcar[this.GetHighwayType()];
|
||||
}
|
||||
return speedped[this.GetHighwayType()];
|
||||
}
|
||||
|
||||
public bool IsForward()
|
||||
{
|
||||
return this.tags.ContainsKey(tagType.forward) && (bool)this.tags[tagType.forward];
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
namespace OSMDatastructure;
|
||||
|
||||
public class OsmNode
|
||||
{
|
||||
public HashSet<OsmEdge> edges { get; }
|
||||
public Coordinates coordinates { get; }
|
||||
|
||||
public OsmNode? previousPathNode = null;
|
||||
public double currentPathWeight = double.MaxValue;
|
||||
public double directDistanceToGoal = double.MaxValue;
|
||||
|
||||
public OsmNode(float lat, float lon)
|
||||
{
|
||||
this.edges = new();
|
||||
this.coordinates = new Coordinates(lat, lon);
|
||||
}
|
||||
|
||||
public OsmNode(Coordinates coordinates)
|
||||
{
|
||||
this.edges = new();
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
public OsmEdge? GetEdgeToNode(OsmNode n)
|
||||
{
|
||||
foreach (OsmEdge e in this.edges)
|
||||
if (e.neighborCoordinates.Equals(n.coordinates))
|
||||
return e;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj != null && obj.GetType() == this.GetType() && ((OsmNode)obj).coordinates.Equals(this.coordinates);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(coordinates);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
"NODE {0} Edges-Count: {1} previousPathNode: {2} currentPathWeight: {3} directDistanceToGoal: {4}",
|
||||
this.coordinates.ToString(), this.edges.Count, this.previousPathNode.coordinates.ToString(),
|
||||
this.currentPathWeight, this.directDistanceToGoal);
|
||||
}
|
||||
}
|
@ -1,26 +1,58 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using OSMDatastructure.Graph;
|
||||
|
||||
namespace OSMDatastructure;
|
||||
|
||||
[Serializable]
|
||||
public class Region
|
||||
{
|
||||
public const float regionSize = 0.01f;
|
||||
public readonly HashSet<OsmNode> nodes = new();
|
||||
public ulong regionHash { get; }
|
||||
[JsonIgnore]public const float RegionSize = 0.025f;
|
||||
[JsonIgnore]public static readonly JsonSerializerOptions serializerOptions = new()
|
||||
{
|
||||
IncludeFields = true,
|
||||
MaxDepth = 32,
|
||||
IgnoreReadOnlyFields = false//,
|
||||
//WriteIndented = true
|
||||
};
|
||||
|
||||
[JsonRequired]public HashSet<OsmNode> nodes { get; set; }
|
||||
public ulong regionHash { get; }
|
||||
[JsonRequired]public TagManager tagManager { get; set; }
|
||||
|
||||
[JsonConstructor]
|
||||
public Region(ulong regionHash)
|
||||
{
|
||||
this.regionHash = regionHash;
|
||||
tagManager = new TagManager();
|
||||
nodes = new HashSet<OsmNode>();
|
||||
}
|
||||
|
||||
public Region(Coordinates regionCoordinates)
|
||||
public Region(Coordinates coordinates)
|
||||
{
|
||||
this.regionHash = regionCoordinates.GetRegionHash();
|
||||
regionHash = Coordinates.GetRegionHashCode(coordinates);
|
||||
tagManager = new TagManager();
|
||||
nodes = new HashSet<OsmNode>();
|
||||
}
|
||||
|
||||
public bool ContainsNode(ulong id)
|
||||
{
|
||||
return nodes.Any(node => node.nodeId == id);
|
||||
}
|
||||
|
||||
public bool ContainsNode(Coordinates coordinates)
|
||||
{
|
||||
return nodes.Any(node => node.coordinates.Equals(coordinates));
|
||||
}
|
||||
|
||||
public OsmNode? GetNode(ulong id)
|
||||
{
|
||||
return ContainsNode(id) ? nodes.First(node => node.nodeId == id) : null;
|
||||
}
|
||||
|
||||
public OsmNode? GetNode(Coordinates coordinates)
|
||||
{
|
||||
foreach(OsmNode node in this.nodes)
|
||||
if (node.coordinates.Equals(coordinates))
|
||||
return node;
|
||||
return null;
|
||||
return ContainsNode(coordinates) ? nodes.First(node => node.coordinates.Equals(coordinates)) : null;
|
||||
}
|
||||
|
||||
}
|
182
OSMDatastructure/Tag.cs
Normal file
182
OSMDatastructure/Tag.cs
Normal file
@ -0,0 +1,182 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace OSMDatastructure;
|
||||
|
||||
[Serializable]
|
||||
public class Tag
|
||||
{
|
||||
public TagType key { get; }
|
||||
public dynamic value { get; }
|
||||
|
||||
[JsonConstructor]
|
||||
public Tag(TagType key, dynamic value)
|
||||
{
|
||||
this.key = key;
|
||||
if (value is JsonElement)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case TagType.highway:
|
||||
this.value = value.GetByte();
|
||||
break;
|
||||
case TagType.maxspeed:
|
||||
this.value = value.GetByte();
|
||||
break;
|
||||
case TagType.forward:
|
||||
case TagType.oneway:
|
||||
this.value = value.GetBoolean();
|
||||
break;
|
||||
case TagType.id:
|
||||
this.value = value.GetUInt64();
|
||||
break;
|
||||
case TagType.name:
|
||||
case TagType.tagref:
|
||||
this.value = value.GetString();
|
||||
break;
|
||||
default:
|
||||
this.value = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static HashSet<Tag> ConvertToTags(string key, string value)
|
||||
{
|
||||
HashSet<Tag> ret = new HashSet<Tag>();
|
||||
switch (key)
|
||||
{
|
||||
case "highway":
|
||||
try
|
||||
{
|
||||
ret.Add(new Tag(TagType.highway, (WayType)Enum.Parse(typeof(WayType), value, true)));
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
ret.Add(new Tag(TagType.highway, WayType.unclassified));
|
||||
}
|
||||
break;
|
||||
case "maxspeed":
|
||||
case "maxspeed:max":
|
||||
try
|
||||
{
|
||||
byte speed = Convert.ToByte(value);
|
||||
if (speed != 255)
|
||||
ret.Add(new Tag(TagType.maxspeed, speed));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ret.Add(new Tag(TagType.maxspeed, byte.MinValue));
|
||||
}
|
||||
break;
|
||||
case "oneway":
|
||||
switch (value)
|
||||
{
|
||||
case "yes":
|
||||
ret.Add(new Tag(TagType.oneway, true));
|
||||
break;
|
||||
case "-1":
|
||||
ret.Add(new Tag(TagType.forward, false));
|
||||
ret.Add(new Tag(TagType.oneway, true));
|
||||
break;
|
||||
case "no":
|
||||
ret.Add(new Tag(TagType.oneway, false));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case "name":
|
||||
ret.Add(new Tag(TagType.name, value));
|
||||
break;
|
||||
case "ref":
|
||||
ret.Add(new Tag(TagType.tagref, value));
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"TAG {key.ToString()} {value.ToString()}";
|
||||
}
|
||||
|
||||
public enum TagType : byte
|
||||
{
|
||||
highway, oneway, footway, sidewalk, cycleway, busway, forward, maxspeed, name, surface, lanes, access, tracktype, id, tagref
|
||||
}
|
||||
|
||||
public static readonly Dictionary<WayType, byte> defaultSpeedCar = new() {
|
||||
{ WayType.NONE, 0 },
|
||||
{ WayType.motorway, 130 },
|
||||
{ WayType.motorroad, 100 },
|
||||
{ WayType.trunk, 85 },
|
||||
{ WayType.primary, 65 },
|
||||
{ WayType.secondary, 60 },
|
||||
{ WayType.tertiary, 50 },
|
||||
{ WayType.unclassified, 30 },
|
||||
{ WayType.residential, 20 },
|
||||
{ WayType.motorway_link, 60 },
|
||||
{ WayType.trunk_link, 50 },
|
||||
{ WayType.primary_link, 50 },
|
||||
{ WayType.secondary_link, 50 },
|
||||
{ WayType.tertiary_link, 30 },
|
||||
{ WayType.living_street, 10 },
|
||||
{ WayType.service, 0 },
|
||||
{ WayType.pedestrian, 0 },
|
||||
{ WayType.track, 0 },
|
||||
{ WayType.bus_guideway, 0 },
|
||||
{ WayType.escape, 0 },
|
||||
{ WayType.raceway, 0 },
|
||||
{ WayType.road, 20 },
|
||||
{ WayType.busway, 0 },
|
||||
{ WayType.footway, 0 },
|
||||
{ WayType.bridleway, 0 },
|
||||
{ WayType.steps, 0 },
|
||||
{ WayType.corridor, 0 },
|
||||
{ WayType.path, 0 },
|
||||
{ WayType.cycleway, 0 },
|
||||
{ WayType.construction, 0 }
|
||||
};
|
||||
|
||||
public static readonly Dictionary<WayType, byte> defaultSpeedPedestrian = new() {
|
||||
{ WayType.NONE, 0 },
|
||||
{ WayType.motorway, 0 },
|
||||
{ WayType.trunk, 0 },
|
||||
{ WayType.primary, 0 },
|
||||
{ WayType.secondary, 0 },
|
||||
{ WayType.tertiary, 2 },
|
||||
{ WayType.unclassified, 1 },
|
||||
{ WayType.residential, 4 },
|
||||
{ WayType.motorway_link, 0 },
|
||||
{ WayType.trunk_link, 0 },
|
||||
{ WayType.primary_link, 0 },
|
||||
{ WayType.secondary_link, 0 },
|
||||
{ WayType.tertiary_link, 0 },
|
||||
{ WayType.living_street, 5 },
|
||||
{ WayType.service, 2 },
|
||||
{ WayType.pedestrian, 5 },
|
||||
{ WayType.track, 1 },
|
||||
{ WayType.bus_guideway, 0 },
|
||||
{ WayType.escape, 1 },
|
||||
{ WayType.raceway, 0 },
|
||||
{ WayType.road, 2 },
|
||||
{ WayType.busway, 0 },
|
||||
{ WayType.footway, 4 },
|
||||
{ WayType.bridleway, 1 },
|
||||
{ WayType.steps, 2 },
|
||||
{ WayType.corridor, 3 },
|
||||
{ WayType.path, 4 },
|
||||
{ WayType.cycleway, 1 },
|
||||
{ WayType.construction, 0 }
|
||||
};
|
||||
// ReSharper disable InconsistentNaming
|
||||
public enum WayType : byte { NONE, motorway, motorroad, 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, any }
|
||||
}
|
58
OSMDatastructure/TagManager.cs
Normal file
58
OSMDatastructure/TagManager.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace OSMDatastructure;
|
||||
|
||||
[Serializable]
|
||||
public class TagManager
|
||||
{
|
||||
[JsonRequired]public Dictionary<ulong, HashSet<Tag>> wayTagSets { get; set; }
|
||||
|
||||
public TagManager()
|
||||
{
|
||||
wayTagSets = new();
|
||||
}
|
||||
|
||||
public bool ContainsKey(ulong wayId, Tag.TagType key)
|
||||
{
|
||||
return wayTagSets.ContainsKey(wayId) && wayTagSets[wayId].Any(tag => tag.key == key);
|
||||
}
|
||||
|
||||
public object? GetTag(ulong wayId, Tag.TagType key)
|
||||
{
|
||||
return ContainsKey(wayId, key) ? wayTagSets[wayId].First(tag => tag.key == key).value : null;
|
||||
}
|
||||
|
||||
public void AddTag(ulong wayId, string key, string value)
|
||||
{
|
||||
HashSet<Tag> pTags = Tag.ConvertToTags(key, value);
|
||||
if(pTags.Count > 0)
|
||||
foreach (Tag pTag in pTags)
|
||||
AddTag(wayId, pTag);
|
||||
}
|
||||
|
||||
public void AddTag(ulong wayId, Tag tag)
|
||||
{
|
||||
if(!wayTagSets.ContainsKey(wayId))
|
||||
wayTagSets.Add(wayId, new HashSet<Tag>());
|
||||
HashSet<Tag> wayTags = wayTagSets[wayId];
|
||||
if (!wayTags.Any(wayTag => wayTag.key == tag.key))
|
||||
{
|
||||
wayTags.Add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTag(ulong wayId, KeyValuePair<Tag.TagType, dynamic> tag)
|
||||
{
|
||||
AddTag(wayId, new Tag(tag.Key, tag.Value));
|
||||
}
|
||||
|
||||
public HashSet<Tag>? GetTagsForWayId(ulong wayId)
|
||||
{
|
||||
return wayTagSets.TryGetValue(wayId, out HashSet<Tag>? value) ? value : null;
|
||||
}
|
||||
|
||||
public bool ContainsWay(ulong wayId)
|
||||
{
|
||||
return wayTagSets.ContainsKey(wayId);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using OSMDatastructure;
|
||||
using OSMDatastructure.Graph;
|
||||
|
||||
namespace Pathfinding
|
||||
namespace OSMDatastructure
|
||||
{
|
||||
public struct Utils
|
||||
{
|
||||
@ -65,12 +65,12 @@ namespace Pathfinding
|
||||
return d;
|
||||
}
|
||||
|
||||
private static double DegreesToRadians(double deg)
|
||||
public static double DegreesToRadians(double deg)
|
||||
{
|
||||
return deg * Math.PI / 180.0;
|
||||
}
|
||||
|
||||
private static double RadiansToDegrees(double rad)
|
||||
public static double RadiansToDegrees(double rad)
|
||||
{
|
||||
return rad * 180.0 / Math.PI;
|
||||
}
|
@ -6,6 +6,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OSMDatastructure", "OSMData
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pathfinding", "Pathfinding\Pathfinding.csproj", "{D4AA1C47-98D4-415C-B026-3BC25B6B6F9D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{1D364F40-1681-4D36-A625-83B324F6AC89}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderPath", "RenderPath\RenderPath.csproj", "{54CAC127-4EB6-4E06-A5C8-35343C5FF76A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -24,5 +28,13 @@ Global
|
||||
{D4AA1C47-98D4-415C-B026-3BC25B6B6F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D4AA1C47-98D4-415C-B026-3BC25B6B6F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D4AA1C47-98D4-415C-B026-3BC25B6B6F9D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1D364F40-1681-4D36-A625-83B324F6AC89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1D364F40-1681-4D36-A625-83B324F6AC89}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1D364F40-1681-4D36-A625-83B324F6AC89}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1D364F40-1681-4D36-A625-83B324F6AC89}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{54CAC127-4EB6-4E06-A5C8-35343C5FF76A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{54CAC127-4EB6-4E06-A5C8-35343C5FF76A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{54CAC127-4EB6-4E06-A5C8-35343C5FF76A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{54CAC127-4EB6-4E06-A5C8-35343C5FF76A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -1,4 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=busway/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=maxspeed/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=oberbayern/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
@ -1,5 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=_002Fhome_002Fglax_002FRiderProjects_002FGeo_002DGraph_002FGeo_002DGraph_002Fobj_002FDebug_002Fnet6_002E0_002FGeo_002DGraph_002Edll/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ferrata/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=glax/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=guideway/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
@ -1,25 +0,0 @@
|
||||
using OSMDatastructure;
|
||||
|
||||
namespace OSMImporter;
|
||||
|
||||
internal class RegionCollection
|
||||
{
|
||||
private readonly Dictionary<ulong, Region> _regions = new();
|
||||
|
||||
public Region GetRegion(Coordinates coordinates)
|
||||
{
|
||||
if(this._regions.ContainsKey(coordinates.GetRegionHash()))
|
||||
return this._regions[coordinates.GetRegionHash()];
|
||||
else
|
||||
{
|
||||
Region newRegion = new Region(coordinates);
|
||||
this._regions.Add(newRegion.regionHash, value: newRegion);
|
||||
return newRegion;
|
||||
}
|
||||
}
|
||||
|
||||
public Region[] GetAllRegions()
|
||||
{
|
||||
return this._regions.Values.ToArray();
|
||||
}
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using OSMDatastructure;
|
||||
|
||||
namespace OSMImporter
|
||||
{
|
||||
public static class XmlImporter
|
||||
{
|
||||
|
||||
private static readonly XmlReaderSettings readerSettings = new()
|
||||
{
|
||||
IgnoreWhitespace = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
|
||||
private static readonly NumberFormatInfo coordinateFormat = new NumberFormatInfo()
|
||||
{
|
||||
NumberDecimalSeparator = "."
|
||||
};
|
||||
|
||||
public static void Split(string xmlFilePath, string outputFolderPath)
|
||||
{
|
||||
if (!File.Exists(xmlFilePath))
|
||||
throw new FileNotFoundException();
|
||||
FileStream xmlFileStream = File.OpenRead(xmlFilePath);
|
||||
|
||||
Console.WriteLine("Reading ways once...");
|
||||
Dictionary<ulong, Node?> nodes = ReturnNodeIdDictionary(XmlReader.Create(xmlFileStream, readerSettings));
|
||||
|
||||
xmlFileStream.Position = 0;
|
||||
RegionCollection regionCollection = new RegionCollection();
|
||||
|
||||
Console.WriteLine("Reading nodes...");
|
||||
LoadNodesIntoDictionary(ref nodes, XmlReader.Create(xmlFileStream, readerSettings), ref regionCollection);
|
||||
|
||||
xmlFileStream.Position = 0;
|
||||
|
||||
Console.WriteLine("Reading ways twice...");
|
||||
CreateConnections(XmlReader.Create(xmlFileStream, readerSettings), ref nodes);
|
||||
|
||||
Console.WriteLine("Writing...");
|
||||
|
||||
foreach(Region region in regionCollection.GetAllRegions())
|
||||
WriteRegion(region, outputFolderPath);
|
||||
}
|
||||
|
||||
private static Dictionary<ulong, Node?> ReturnNodeIdDictionary(XmlReader xmlReader)
|
||||
{
|
||||
Dictionary<ulong, Node?> retSet = new Dictionary<ulong, Node?>();
|
||||
while (xmlReader.ReadToFollowing("way"))
|
||||
{
|
||||
XmlReader wayReader = xmlReader.ReadSubtree();
|
||||
bool addNodes = false;
|
||||
HashSet<string> nodeIds = new();
|
||||
while (wayReader.Read())
|
||||
{
|
||||
if (xmlReader.IsStartElement() && xmlReader.Name.Equals("nd"))
|
||||
{
|
||||
nodeIds.Add(wayReader.GetAttribute("ref")!);
|
||||
}
|
||||
else if (xmlReader.IsStartElement() && !addNodes && xmlReader.Name.Equals("tag") && xmlReader.GetAttribute("k")!.Equals("highway"))
|
||||
{
|
||||
addNodes = true;
|
||||
}
|
||||
}
|
||||
wayReader.Close();
|
||||
|
||||
if (addNodes)
|
||||
{
|
||||
foreach (string nodeId in nodeIds)
|
||||
{
|
||||
retSet.TryAdd(Convert.ToUInt64(nodeId), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmlReader.Close();
|
||||
return retSet;
|
||||
}
|
||||
|
||||
private static void LoadNodesIntoDictionary(ref Dictionary<ulong, Node?> nodes, XmlReader xmlReader, ref RegionCollection regionCollection)
|
||||
{
|
||||
while (xmlReader.ReadToFollowing("node"))
|
||||
{
|
||||
ulong id = Convert.ToUInt64(xmlReader.GetAttribute("id"));
|
||||
if (nodes.ContainsKey(id))
|
||||
{
|
||||
float lat = Convert.ToSingle(xmlReader.GetAttribute("lat")!, coordinateFormat);
|
||||
float lon = Convert.ToSingle(xmlReader.GetAttribute("lon")!, coordinateFormat);
|
||||
Node newNode = new Node(lat, lon);
|
||||
nodes[id] = newNode;
|
||||
regionCollection.GetRegion(newNode).AddNode(id, newNode);
|
||||
}
|
||||
}
|
||||
xmlReader.Close();
|
||||
}
|
||||
|
||||
private static void CreateConnections(XmlReader xmlReader, ref Dictionary<ulong, Node?> nodes)
|
||||
{
|
||||
while (xmlReader.ReadToFollowing("way"))
|
||||
{
|
||||
XmlReader wayReader = xmlReader.ReadSubtree();
|
||||
HashSet<ulong> nodesInWay = new ();
|
||||
Dictionary<string, string> tags = new ();
|
||||
bool addNodes = false;
|
||||
while (wayReader.Read())
|
||||
{
|
||||
if (wayReader.IsStartElement() && wayReader.Name.Equals("nd"))
|
||||
{
|
||||
nodesInWay.Add(Convert.ToUInt64(wayReader.GetAttribute("ref")));
|
||||
}
|
||||
else if (wayReader.IsStartElement() && wayReader.Name.Equals("tag"))
|
||||
{
|
||||
if (wayReader.GetAttribute("k")!.Equals("highway"))
|
||||
{
|
||||
addNodes = true;
|
||||
}
|
||||
tags.Add(wayReader.GetAttribute("k")!, value: wayReader.GetAttribute("v")!);
|
||||
}
|
||||
}
|
||||
wayReader.Close();
|
||||
|
||||
if (addNodes)
|
||||
{
|
||||
ConnectNodesOfWay(nodes, nodesInWay.ToArray(), tags);
|
||||
}
|
||||
}
|
||||
xmlReader.Close();
|
||||
}
|
||||
|
||||
private static void ConnectNodesOfWay(Dictionary<ulong, Node?> nodes, ulong[] nodeIds,
|
||||
Dictionary<string, string> tags)
|
||||
{
|
||||
string oneWayString = tags.ContainsKey("oneway") ? tags["oneway"] : "no";
|
||||
bool oneWay = false;
|
||||
bool forward = true;
|
||||
switch (oneWayString)
|
||||
{
|
||||
case "yes":
|
||||
oneWay = true;
|
||||
break;
|
||||
case "-1":
|
||||
forward = false;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeIds.Length - 1; i++)
|
||||
{
|
||||
if (oneWay)
|
||||
{
|
||||
if(nodes.ContainsKey(nodeIds[i]))
|
||||
nodes[nodeIds[i]]!.AddConnection(new Connection(nodeIds[i + 1], nodes[nodeIds[i + 1]]!, tags));
|
||||
if(nodes.ContainsKey(nodeIds[i + 1]))
|
||||
nodes[nodeIds[i + 1]]!.AddConnection(new Connection(nodeIds[i], nodes[nodeIds[i]]!, tags));
|
||||
}
|
||||
else if (forward)
|
||||
{
|
||||
if(nodes.ContainsKey(nodeIds[i]))
|
||||
nodes[nodeIds[i]]!.AddConnection(new Connection(nodeIds[i + 1], nodes[nodeIds[i + 1]]!, tags));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(nodes.ContainsKey(nodeIds[i + 1]))
|
||||
nodes[nodeIds[i + 1]]!.AddConnection(new Connection(nodeIds[i], nodes[nodeIds[i]]!, tags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteRegion(Region region, string outputFolderPath)
|
||||
{
|
||||
if (!Directory.Exists(outputFolderPath))
|
||||
Directory.CreateDirectory(outputFolderPath);
|
||||
string fileName = region.regionHash.ToString();
|
||||
string fullPath = Path.Combine(outputFolderPath, fileName);
|
||||
if (!File.Exists(fullPath))
|
||||
File.Create(fullPath).Close();
|
||||
FileStream fileStream = new FileStream(fullPath, FileMode.Append);
|
||||
fileStream.Write(ByteConverter.GetBytes(region));
|
||||
fileStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
38
Pathfinding/PathNode.cs
Normal file
38
Pathfinding/PathNode.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using OSMDatastructure;
|
||||
using OSMDatastructure.Graph;
|
||||
|
||||
namespace Pathfinding;
|
||||
|
||||
public class PathNode : OsmNode
|
||||
{
|
||||
[JsonInclude]public Dictionary<string, string> tags = new();
|
||||
|
||||
[JsonConstructor]
|
||||
public PathNode(ulong nodeId, Coordinates coordinates, Dictionary<string, string> tags) : base(nodeId, coordinates)
|
||||
{
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
public PathNode(ulong nodeId, float lat, float lon) : base(nodeId, lat, lon)
|
||||
{
|
||||
}
|
||||
|
||||
public PathNode(ulong nodeId, Coordinates coordinates) : base(nodeId, coordinates)
|
||||
{
|
||||
}
|
||||
|
||||
public static PathNode? FromOsmNode(OsmNode? node, HashSet<Tag>? tags)
|
||||
{
|
||||
if (node is null)
|
||||
return null;
|
||||
PathNode retNode = new(node.nodeId, node.coordinates);
|
||||
if (tags != null)
|
||||
foreach (Tag tag in tags)
|
||||
{
|
||||
retNode.tags.Add(tag.key.ToString(), tag.value.ToString());
|
||||
}
|
||||
|
||||
return retNode;
|
||||
}
|
||||
}
|
27
Pathfinding/PathResult.cs
Normal file
27
Pathfinding/PathResult.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Pathfinding;
|
||||
|
||||
public class PathResult
|
||||
{
|
||||
[JsonInclude]public double distance;
|
||||
[JsonInclude]public double weight;
|
||||
[JsonInclude]public TimeSpan calcTime;
|
||||
[JsonInclude]public List<PathNode> pathNodes;
|
||||
|
||||
[JsonConstructor]
|
||||
public PathResult(TimeSpan calcTime, List<PathNode> pathNodes, double distance, double weight)
|
||||
{
|
||||
this.calcTime = calcTime;
|
||||
this.pathNodes = pathNodes;
|
||||
this.distance = distance;
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public static PathResult PathresultFromFile(string filePath)
|
||||
{
|
||||
return JsonSerializer.Deserialize<PathResult>(new FileStream(filePath, FileMode.Open, FileAccess.Read,
|
||||
FileShare.Read))!;
|
||||
}
|
||||
}
|
@ -1,97 +1,228 @@
|
||||
using OSMDatastructure;
|
||||
using OSMImporter;
|
||||
using System.Text.Json;
|
||||
using OSMDatastructure;
|
||||
using OSMDatastructure.Graph;
|
||||
using static OSMDatastructure.Tag;
|
||||
|
||||
namespace Pathfinding;
|
||||
|
||||
public class Pathfinder
|
||||
{
|
||||
|
||||
public static List<OsmNode> CustomAStar(string workingDir, Coordinates start, Coordinates goal)
|
||||
public RegionManager regionManager;
|
||||
public PathResult? pathResult;
|
||||
public Dictionary<OsmNode, double>? gScore;
|
||||
private Dictionary<OsmNode, OsmNode>? _cameFromDict;
|
||||
private SpeedType _speedType;
|
||||
private double roadPriorityFactor, turnAngle;
|
||||
|
||||
public Pathfinder(string workingDirectory, double roadPriorityFactor, double turnAngle)
|
||||
{
|
||||
RegionManager regionManager = new RegionManager(workingDir);
|
||||
Region startRegion, goalRegion;
|
||||
OsmNode? startNode, goalNode;
|
||||
try
|
||||
{
|
||||
startRegion = regionManager.GetRegion(start);
|
||||
goalRegion = regionManager.GetRegion(goal);
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new Exception(string.Format("No region at coordinates {0}", e.FileName), e);
|
||||
}
|
||||
|
||||
startNode = ClosestNodeToCoordinates(start, startRegion);
|
||||
goalNode = ClosestNodeToCoordinates(goal, goalRegion);
|
||||
if (startNode == null || goalNode == null)
|
||||
return new List<OsmNode>();
|
||||
|
||||
List<OsmNode> toVisit = new() { startNode };
|
||||
OsmNode closestNodeToGoal;
|
||||
bool stop = false;
|
||||
|
||||
while (toVisit.Count > 0 && !stop)
|
||||
{
|
||||
Console.WriteLine("toVisit length: {0}", toVisit.Count.ToString());
|
||||
closestNodeToGoal = toVisit.First();
|
||||
foreach (OsmNode node in toVisit)
|
||||
{
|
||||
if (node.directDistanceToGoal.Equals(double.MaxValue))
|
||||
{
|
||||
node.directDistanceToGoal = Utils.DistanceBetween(node, goalNode);
|
||||
}
|
||||
if (node.directDistanceToGoal < closestNodeToGoal.directDistanceToGoal)
|
||||
{
|
||||
closestNodeToGoal = node;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (OsmEdge connection in closestNodeToGoal.edges)
|
||||
{
|
||||
OsmNode? neighbor = regionManager.GetNode(connection.neighborCoordinates);
|
||||
Console.WriteLine(neighbor);
|
||||
if (neighbor != null && neighbor.currentPathWeight < closestNodeToGoal.currentPathWeight + Utils.DistanceBetween(closestNodeToGoal, neighbor))
|
||||
{
|
||||
neighbor.previousPathNode = closestNodeToGoal;
|
||||
neighbor.currentPathWeight = closestNodeToGoal.currentPathWeight +
|
||||
Utils.DistanceBetween(closestNodeToGoal, neighbor);
|
||||
|
||||
if (neighbor.Equals(goalNode))
|
||||
stop = true;
|
||||
else
|
||||
toVisit.Add(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
toVisit.Remove(closestNodeToGoal);
|
||||
}
|
||||
|
||||
List<OsmNode> path = new();
|
||||
OsmNode? currentNode = goalNode;
|
||||
while (currentNode != null && !currentNode.Equals(startNode))
|
||||
{
|
||||
path.Add(currentNode);
|
||||
currentNode = currentNode.previousPathNode;
|
||||
}
|
||||
path.Add(startNode);
|
||||
|
||||
return path;
|
||||
if (!Path.Exists(workingDirectory))
|
||||
throw new DirectoryNotFoundException(workingDirectory);
|
||||
regionManager = new(workingDirectory);
|
||||
this.roadPriorityFactor = roadPriorityFactor;
|
||||
this.turnAngle = turnAngle;
|
||||
}
|
||||
|
||||
private static OsmNode? ClosestNodeToCoordinates(Coordinates coordinates, Region region)
|
||||
public Pathfinder(RegionManager regionManager, double roadPriorityFactor, double turnAngle)
|
||||
{
|
||||
OsmNode? closest = null;
|
||||
double distance = double.MaxValue;
|
||||
foreach (OsmNode node in region.nodes)
|
||||
this.regionManager = regionManager;
|
||||
this.roadPriorityFactor = roadPriorityFactor;
|
||||
this.turnAngle = turnAngle;
|
||||
}
|
||||
|
||||
public Pathfinder AStar(Coordinates startCoordinates, Coordinates goalCoordinates, SpeedType vehicle, double extraTime)
|
||||
{
|
||||
DateTime startCalc = DateTime.Now;
|
||||
_speedType = vehicle;
|
||||
OsmNode? startNode = regionManager.ClosestNodeToCoordinates(startCoordinates, _speedType);
|
||||
OsmNode? goalNode = regionManager.ClosestNodeToCoordinates(goalCoordinates, _speedType);
|
||||
if (startNode is null || goalNode is null)
|
||||
{
|
||||
double nodeDistance = Utils.DistanceBetween(node, coordinates);
|
||||
if (nodeDistance < distance)
|
||||
pathResult = new(DateTime.Now - startCalc, new List<PathNode>(),0 ,0);
|
||||
return this;
|
||||
}
|
||||
|
||||
RPriorityQueue<OsmNode, double> openSetfScore = new();
|
||||
openSetfScore.Enqueue(startNode, 0);
|
||||
gScore = new() { { startNode, 0 } };
|
||||
_cameFromDict = new();
|
||||
|
||||
bool found = false;
|
||||
bool stop = false;
|
||||
TimeSpan firstFound = TimeSpan.MaxValue;
|
||||
double maxGscore = double.MaxValue;
|
||||
|
||||
while (openSetfScore.Count > 0 && !stop)
|
||||
{
|
||||
OsmNode currentNode = openSetfScore.Dequeue()!;
|
||||
if (currentNode.Equals(goalNode))
|
||||
{
|
||||
closest = node;
|
||||
distance = nodeDistance;
|
||||
if (!found)
|
||||
{
|
||||
firstFound = DateTime.Now - startCalc;
|
||||
found = true;
|
||||
Console.WriteLine($"First: {firstFound} Multiplied by {extraTime}: {firstFound.Multiply(extraTime)}");
|
||||
}
|
||||
maxGscore = gScore[goalNode];
|
||||
}
|
||||
|
||||
if (found && DateTime.Now - startCalc > firstFound.Multiply(extraTime))
|
||||
stop = true;
|
||||
|
||||
foreach (OsmEdge edge in currentNode.edges)
|
||||
{
|
||||
OsmNode? neighbor = regionManager.GetNode(edge.neighborId, edge.neighborRegion);
|
||||
if (neighbor is not null)
|
||||
{
|
||||
double tentativeGScore = gScore[currentNode] + Weight(currentNode, neighbor, edge);
|
||||
gScore.TryAdd(neighbor, double.MaxValue);
|
||||
if ((!found || (found && tentativeGScore < maxGscore)) && tentativeGScore < gScore[neighbor])
|
||||
{
|
||||
if(!_cameFromDict.TryAdd(neighbor, currentNode))
|
||||
_cameFromDict[neighbor] = currentNode;
|
||||
gScore[neighbor] = tentativeGScore;
|
||||
double h = Heuristic(currentNode, neighbor, goalNode, edge);
|
||||
openSetfScore.Enqueue(neighbor, tentativeGScore + h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
TimeSpan calcTime = DateTime.Now - startCalc;
|
||||
if (!found)
|
||||
{
|
||||
pathResult = new(DateTime.Now - startCalc, new List<PathNode>(),0 ,0);
|
||||
Console.Write("No path found.");
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
pathResult = GetPath(goalNode, calcTime);
|
||||
}
|
||||
Console.WriteLine($"Path found. {calcTime} PathLength {pathResult.pathNodes.Count} VisitedNodes {gScore.Count} Distance {pathResult.distance} Duration {pathResult.weight}");
|
||||
return this;
|
||||
}
|
||||
|
||||
private double Weight(OsmNode currentNode, OsmNode neighborNode, OsmEdge edge)
|
||||
{
|
||||
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];
|
||||
Vector v1 = new(currentNode, previousNode);
|
||||
Vector v2 = new(currentNode, neighborNode);
|
||||
double nodeAngle = v1.Angle(v2);
|
||||
if (nodeAngle < turnAngle)
|
||||
angle = 0;
|
||||
else
|
||||
angle = nodeAngle / 180;
|
||||
}
|
||||
double prio = regionManager.GetPriorityForVehicle(_speedType,edge, currentNode) * roadPriorityFactor;
|
||||
|
||||
return distance / (speed * angle + prio + 1);
|
||||
}
|
||||
|
||||
private double Heuristic(OsmNode currentNode, OsmNode neighborNode, OsmNode goalNode, OsmEdge edge)
|
||||
{
|
||||
if (neighborNode.Equals(goalNode)) return 0;
|
||||
double priority = regionManager.GetPriorityForVehicle(_speedType, edge, currentNode);
|
||||
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))
|
||||
{
|
||||
OsmNode previousNode = _cameFromDict[currentNode];
|
||||
Vector v1 = new(currentNode, previousNode);
|
||||
Vector v2 = new(currentNode, neighborNode);
|
||||
double nodeAngle = v1.Angle(v2);
|
||||
if (nodeAngle < turnAngle)
|
||||
angle = 0;
|
||||
else
|
||||
angle = nodeAngle / 180;
|
||||
}
|
||||
|
||||
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();
|
||||
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]))
|
||||
{
|
||||
OsmEdge? currentEdge = _cameFromDict[currentNode].edges.FirstOrDefault(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);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private class Vector
|
||||
{
|
||||
public readonly float x, y;
|
||||
|
||||
public Vector(float x, float y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Vector(OsmNode n1, OsmNode n2)
|
||||
{
|
||||
this.x = n1.coordinates.longitude - n2.coordinates.longitude;
|
||||
this.y = n1.coordinates.latitude - n2.coordinates.latitude;
|
||||
}
|
||||
|
||||
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);
|
||||
double ang = Math.Acos(dotProd / (v1L * v2L));
|
||||
if (ang.Equals(double.NaN))
|
||||
return 0;
|
||||
double angle = Utils.RadiansToDegrees(ang);
|
||||
return angle;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,4 +10,8 @@
|
||||
<ProjectReference Include="..\OSMDatastructure\OSMDatastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.Serialization.Json" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
47
Pathfinding/RPriorityQueue.cs
Normal file
47
Pathfinding/RPriorityQueue.cs
Normal file
@ -0,0 +1,47 @@
|
||||
namespace Pathfinding;
|
||||
|
||||
public class RPriorityQueue<TKey, TPriority> where TKey : notnull
|
||||
{
|
||||
public Dictionary<TKey, TPriority> queue;
|
||||
public int Count => queue.Count;
|
||||
|
||||
public RPriorityQueue()
|
||||
{
|
||||
queue = new();
|
||||
}
|
||||
|
||||
public void Enqueue(TKey key, TPriority priority)
|
||||
{
|
||||
if (!queue.TryAdd(key, priority))
|
||||
queue[key] = priority;
|
||||
}
|
||||
|
||||
public TKey Dequeue()
|
||||
{
|
||||
TKey retKey = queue.MinBy(item => item.Value).Key;
|
||||
queue.Remove(retKey);
|
||||
return retKey;
|
||||
}
|
||||
|
||||
public int Remove(IEnumerable<TKey> elements)
|
||||
{
|
||||
int before = Count;
|
||||
queue = queue.Where(queueitem => !elements.Contains(queueitem.Key))
|
||||
.ToDictionary(item => item.Key, item => item.Value);
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
using System.Text.Json;
|
||||
using OSMDatastructure;
|
||||
using Pathfinding;
|
||||
using OSMDatastructure.Graph;
|
||||
using SpeedType = OSMDatastructure.Tag.SpeedType;
|
||||
using WayType = OSMDatastructure.Tag.WayType;
|
||||
|
||||
namespace OSMImporter
|
||||
namespace Pathfinding
|
||||
{
|
||||
public class RegionManager
|
||||
{
|
||||
@ -13,57 +16,185 @@ namespace OSMImporter
|
||||
this.workingDirectory = workingDirectory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks wether the Region is already loaded and returns the Region, or tries to load the Region from file in workingDirectory
|
||||
/// </summary>
|
||||
/// <param name="coordinates">Coordinates of the Region (or within the Region) to load</param>
|
||||
/// <returns>The Region at the specified Coordinates containing Nodes and Connections</returns>
|
||||
/// <exception cref="FileNotFoundException">If the Regionfile can not be found.</exception>
|
||||
public Region GetRegion(Coordinates coordinates)
|
||||
public Region? GetRegion(Coordinates coordinates)
|
||||
{
|
||||
if(this._regions.ContainsKey(coordinates.GetRegionHash()))
|
||||
return this._regions[coordinates.GetRegionHash()];
|
||||
else
|
||||
return GetRegion(Coordinates.GetRegionHashCode(coordinates));
|
||||
}
|
||||
|
||||
public Region? GetRegion(ulong id)
|
||||
{
|
||||
if (!_regions.ContainsKey(id))
|
||||
{
|
||||
Region loadedRegion = LoadRegion(coordinates);
|
||||
this._regions.Add(loadedRegion.regionHash, value: loadedRegion);
|
||||
return loadedRegion;
|
||||
Region? loadedRegion = RegionFromId(id);
|
||||
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];
|
||||
}
|
||||
|
||||
public Region[] GetAllRegions()
|
||||
{
|
||||
return this._regions.Values.ToArray();
|
||||
return _regions.Values.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="coordinates">Coordinates of the Region (or within the Region) to load</param>
|
||||
/// <returns>The Region at the specified Coordinates containing Nodes and Connections</returns>
|
||||
/// <exception cref="FileNotFoundException">If the Regionfile can not be found.</exception>
|
||||
private Region LoadRegion(Coordinates coordinates)
|
||||
private static Region? RegionFromFile(string filePath)
|
||||
{
|
||||
string fullPath = Path.Combine(workingDirectory, coordinates.GetRegionHash().ToString());
|
||||
Console.WriteLine("Loading {0}", fullPath);
|
||||
if (!File.Exists(fullPath))
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException(string.Format("Region does not exist: {0}", fullPath));
|
||||
//throw new FileNotFoundException(filePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
FileStream fileStream = new FileStream(fullPath, FileMode.Open);
|
||||
|
||||
byte[] regionBytes = new byte[fileStream.Length];
|
||||
int _ = fileStream.Read(regionBytes, 0, regionBytes.Length);
|
||||
fileStream.Close();
|
||||
|
||||
return ByteConverter.ToRegion(regionBytes);
|
||||
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)!;
|
||||
regionFile.Dispose();
|
||||
return retRegion;
|
||||
}
|
||||
|
||||
public OsmNode? GetNode(Coordinates coordinates)
|
||||
private Region? RegionFromId(ulong regionId)
|
||||
{
|
||||
Region regionWithNode = GetRegion(coordinates);
|
||||
return regionWithNode.GetNode(coordinates);
|
||||
string filePath = Path.Join(workingDirectory, $"{regionId}.region");
|
||||
return RegionFromFile(filePath);
|
||||
}
|
||||
|
||||
public OsmNode? GetNode(ulong nodeId, ulong regionId)
|
||||
{
|
||||
Region? r = GetRegion(regionId);
|
||||
return r?.GetNode(nodeId);
|
||||
}
|
||||
|
||||
public bool TestValidConnectionForType(OsmNode node1, OsmNode node2, SpeedType type)
|
||||
{
|
||||
foreach (OsmEdge edge in node1.edges.Where(edge => edge.neighborId.Equals(node2.nodeId)))
|
||||
{
|
||||
return TestValidConnectionForType(node1, edge, type);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TestValidConnectionForType(OsmNode node1, OsmEdge edge, SpeedType type)
|
||||
{
|
||||
if (type == SpeedType.any)
|
||||
return true;
|
||||
byte speed = GetSpeedForEdge(node1, edge.wayId, type);
|
||||
return (speed is not 0);
|
||||
}
|
||||
|
||||
public OsmNode? ClosestNodeToCoordinates(Coordinates coordinates, 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 = true;
|
||||
if (vehicle is not SpeedType.any)
|
||||
{
|
||||
hasConnectionUsingVehicle = false;
|
||||
foreach (OsmEdge edge in node.edges)
|
||||
{
|
||||
if (TestValidConnectionForType(node, edge, vehicle))
|
||||
hasConnectionUsingVehicle = true;
|
||||
}
|
||||
}
|
||||
|
||||
double nodeDistance = Utils.DistanceBetween(node, coordinates);
|
||||
if (hasConnectionUsingVehicle && nodeDistance < distance)
|
||||
{
|
||||
closest = node;
|
||||
distance = nodeDistance;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
public byte GetSpeedForEdge(OsmNode node1, ulong wayId, SpeedType vehicle)
|
||||
{
|
||||
TagManager tags = GetRegion(node1.coordinates)!.tagManager;
|
||||
WayType wayType = (WayType)tags.GetTag(wayId, Tag.TagType.highway)!;
|
||||
byte speed = 0;
|
||||
switch (vehicle)
|
||||
{
|
||||
case SpeedType.pedestrian:
|
||||
speed = Tag.defaultSpeedPedestrian[wayType];
|
||||
return speed;
|
||||
case SpeedType.car:
|
||||
byte? maxSpeed = (byte?)tags.GetTag(wayId, Tag.TagType.maxspeed);
|
||||
speed = Tag.defaultSpeedCar[wayType];
|
||||
return maxSpeed < speed ? (byte)maxSpeed : speed;
|
||||
case SpeedType.any:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public double GetPriorityForVehicle(SpeedType speedType, OsmEdge edge, OsmNode node)
|
||||
{
|
||||
if (speedType == SpeedType.any)
|
||||
return 1;
|
||||
Region region = GetRegion(node.coordinates)!;
|
||||
WayType? wayType = (WayType?)region.tagManager.GetTag(edge.wayId, Tag.TagType.highway);
|
||||
if(wayType is null)
|
||||
return 0;
|
||||
if (speedType == SpeedType.car)
|
||||
{
|
||||
switch (wayType)
|
||||
{
|
||||
case WayType.motorway:
|
||||
case WayType.motorway_link:
|
||||
case WayType.motorroad:
|
||||
return 20;
|
||||
case WayType.trunk:
|
||||
case WayType.trunk_link:
|
||||
case WayType.primary:
|
||||
case WayType.primary_link:
|
||||
return 10;
|
||||
case WayType.secondary:
|
||||
case WayType.secondary_link:
|
||||
case WayType.tertiary:
|
||||
case WayType.tertiary_link:
|
||||
return 6;
|
||||
case WayType.unclassified:
|
||||
case WayType.residential:
|
||||
case WayType.road:
|
||||
case WayType.living_street:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (speedType == SpeedType.pedestrian)
|
||||
{
|
||||
switch (wayType)
|
||||
{
|
||||
case WayType.pedestrian:
|
||||
case WayType.corridor:
|
||||
case WayType.footway:
|
||||
case WayType.path:
|
||||
case WayType.steps:
|
||||
case WayType.residential:
|
||||
case WayType.living_street:
|
||||
return 10;
|
||||
case WayType.service:
|
||||
case WayType.cycleway:
|
||||
case WayType.bridleway:
|
||||
case WayType.road:
|
||||
case WayType.track:
|
||||
case WayType.unclassified:
|
||||
return 5;
|
||||
case WayType.tertiary:
|
||||
case WayType.tertiary_link:
|
||||
case WayType.escape:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
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
|
@ -4,11 +4,16 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>OSMServer</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageReference Include="System.Runtime.Serialization.Json" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OSMDatastructure\OSMDatastructure.csproj" />
|
||||
<ProjectReference Include="..\Pathfinding\Pathfinding.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
194
RenderPath/Renderer.cs
Normal file
194
RenderPath/Renderer.cs
Normal file
@ -0,0 +1,194 @@
|
||||
using System.Drawing;
|
||||
using OSMDatastructure;
|
||||
using OSMDatastructure.Graph;
|
||||
using Pathfinding;
|
||||
|
||||
namespace RenderPath;
|
||||
|
||||
public abstract class Renderer
|
||||
{
|
||||
private const int ImageMaxSize = 20000;
|
||||
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 Bounds? bounds;
|
||||
|
||||
public enum RenderType { png, svg}
|
||||
|
||||
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);
|
||||
|
||||
public static Renderer DrawArea(RegionManager rm, RenderType renderType)
|
||||
{
|
||||
HashSet<OsmNode> nodes = new();
|
||||
foreach (OSMDatastructure.Region r in rm.GetAllRegions())
|
||||
nodes = nodes.Concat(r.nodes).ToHashSet();
|
||||
|
||||
float minLat = nodes.Min(node => node.coordinates.latitude);
|
||||
float minLon = nodes.Min(node => node.coordinates.longitude);
|
||||
float maxLat = nodes.Max(node => node.coordinates.latitude);
|
||||
float maxLon = nodes.Max(node => node.coordinates.longitude);
|
||||
|
||||
float latDiff = maxLat - minLat;
|
||||
float lonDiff = maxLon - minLon;
|
||||
|
||||
float scaleFactor = latDiff > lonDiff ? ImageMaxSize / latDiff : ImageMaxSize / lonDiff;
|
||||
|
||||
int pixelsX = (int)(lonDiff * scaleFactor);
|
||||
int pixelsY = (int)(latDiff * scaleFactor);
|
||||
|
||||
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) / 20;
|
||||
Coordinates c1 = node.coordinates;
|
||||
OsmNode nNode = rm.GetNode(edge.neighborId, edge.neighborRegion)!;
|
||||
Coordinates c2 = nNode.coordinates;
|
||||
|
||||
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, ColorInterp(RoadPrioStart, RoadPrioEnd, priority));
|
||||
}
|
||||
}
|
||||
|
||||
renderer.bounds = new Bounds(minLat,minLon,maxLat,maxLon);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
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 = 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;
|
||||
|
||||
float scaleFactor = latDiff > lonDiff ? ImageMaxSize / latDiff : ImageMaxSize / lonDiff;
|
||||
|
||||
int pixelsX = (int)(lonDiff * scaleFactor);
|
||||
int pixelsY = (int)(latDiff * scaleFactor);
|
||||
|
||||
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];
|
||||
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);
|
||||
}
|
||||
|
||||
renderer.bounds = new Bounds(minLat, minLon, maxLat, maxLon);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public static Renderer DrawGScores(Dictionary<OsmNode, double> gScore, RenderType renderType, Renderer? drawOver)
|
||||
{
|
||||
|
||||
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);
|
||||
|
||||
float latDiff = maxLat - minLat;
|
||||
float lonDiff = maxLon - minLon;
|
||||
|
||||
float scaleFactor = latDiff > lonDiff ? ImageMaxSize / latDiff : ImageMaxSize / lonDiff;
|
||||
|
||||
int pixelsX = (int)(lonDiff * scaleFactor);
|
||||
int pixelsY = (int)(latDiff * scaleFactor);
|
||||
|
||||
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);
|
||||
float x = (kv.Key.coordinates.longitude - minLon) * scaleFactor;
|
||||
float y = (maxLat - kv.Key.coordinates.latitude) * scaleFactor;
|
||||
|
||||
renderer.DrawDot(x, y, PenThickness, ColorInterp(WeightStartColor, WeightEndColor, percentage));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* https://stackoverflow.com/questions/55601338/get-a-color-value-within-a-gradient-based-on-a-value
|
||||
*/
|
||||
private static int LinearInterp(int start, int end, double percentage) => start + (int)Math.Round(percentage * (end - start));
|
||||
private static Color ColorInterp(Color start, Color end, double percentage) =>
|
||||
Color.FromArgb(LinearInterp(start.A, end.A, percentage),
|
||||
LinearInterp(start.R, end.R, percentage),
|
||||
LinearInterp(start.G, end.G, percentage),
|
||||
LinearInterp(start.B, end.B, percentage));
|
||||
}
|
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}";
|
||||
}
|
||||
}
|
51
Server/ConsoleWriter.cs
Normal file
51
Server/ConsoleWriter.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Server;
|
||||
|
||||
public class ConsoleWriter : TextWriter
|
||||
{
|
||||
public override Encoding Encoding { get; }
|
||||
private TextWriter stdOut { get; }
|
||||
private DateTime execStart { get; }
|
||||
public event EventHandler<ConsoleWriterEventArgs>? OnWrite;
|
||||
public event EventHandler<ConsoleWriterEventArgs>? OnWriteLine;
|
||||
|
||||
public ConsoleWriter()
|
||||
{
|
||||
stdOut = Console.Out;
|
||||
execStart = DateTime.Now;
|
||||
Encoding = Encoding.UTF8;
|
||||
}
|
||||
|
||||
public ConsoleWriter(DateTime execStart)
|
||||
{
|
||||
stdOut = Console.Out;
|
||||
this.execStart = execStart;
|
||||
Encoding = Encoding.UTF8;
|
||||
}
|
||||
|
||||
public override void WriteLine(string? text)
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
string dateTimeString = $"[{now.ToUniversalTime():u} ({Math.Floor((now - execStart).TotalMilliseconds):###,###,###})]";
|
||||
if(text is not null)
|
||||
OnWriteLine?.Invoke(this, new ConsoleWriterEventArgs($"{dateTimeString} {text}"));
|
||||
stdOut.WriteLine($"{dateTimeString} {text}");
|
||||
}
|
||||
|
||||
public override void Write(string? text)
|
||||
{
|
||||
if(text is not null)
|
||||
OnWrite?.Invoke(this, new ConsoleWriterEventArgs(text));
|
||||
stdOut.Write(text);
|
||||
}
|
||||
}
|
||||
|
||||
public class ConsoleWriterEventArgs : EventArgs
|
||||
{
|
||||
public string text { get; }
|
||||
public ConsoleWriterEventArgs(string text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
}
|
302
Server/RegionConverter.cs
Normal file
302
Server/RegionConverter.cs
Normal file
@ -0,0 +1,302 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text.Json;
|
||||
using System.Xml;
|
||||
using OSMDatastructure;
|
||||
using OSMDatastructure.Graph;
|
||||
|
||||
namespace Server;
|
||||
|
||||
public class RegionConverter
|
||||
{
|
||||
private static readonly XmlReaderSettings ReaderSettings = new()
|
||||
{
|
||||
IgnoreWhitespace = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
private static readonly NumberFormatInfo decimalInfo = new()
|
||||
{
|
||||
NumberDecimalSeparator = "."
|
||||
};
|
||||
|
||||
public const string NodesFileName = "region.nodes";
|
||||
public const string WaysFileName = "region.ways";
|
||||
public const string TagsFileName = "region.tags";
|
||||
public const string RegionsFileName = ".region";
|
||||
|
||||
public static void ConvertXMLToRegions(string filePath, string outputPath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException();
|
||||
if (!Directory.Exists(outputPath))
|
||||
Directory.CreateDirectory(outputPath);
|
||||
|
||||
Console.WriteLine("Converting Nodes...");
|
||||
FileStream xmlFileStream = new FileStream(filePath, FileMode.Open);
|
||||
Dictionary<ulong, ulong> nodeIdRegionDict = GetNodesAndRegions(XmlReader.Create(xmlFileStream, ReaderSettings), outputPath);
|
||||
xmlFileStream.Position = 0;
|
||||
|
||||
Console.WriteLine("Converting Ways...");
|
||||
ImportWays(XmlReader.Create(xmlFileStream, ReaderSettings), nodeIdRegionDict, outputPath);
|
||||
xmlFileStream.Dispose();
|
||||
|
||||
Console.WriteLine("Combining tmpFiles into region");
|
||||
CombineTmpFiles(outputPath, nodeIdRegionDict.Values.ToHashSet());
|
||||
|
||||
}
|
||||
|
||||
private static Dictionary<ulong, ulong> GetNodesAndRegions(XmlReader xmlReader, string outputPath)
|
||||
{
|
||||
Dictionary<ulong, ulong> nodeRegions = new();
|
||||
Dictionary<ulong, FileStream> regionFileStreams = new();
|
||||
Dictionary<ulong, OsmNode> tmpAllNodes = new();
|
||||
bool isHighway;
|
||||
HashSet<ulong> currentIds = new();
|
||||
while (xmlReader.Read())
|
||||
{
|
||||
if (xmlReader.Name == "node")
|
||||
{
|
||||
ulong id = Convert.ToUInt64(xmlReader.GetAttribute("id")!);
|
||||
float lat = Convert.ToSingle(xmlReader.GetAttribute("lat")!, decimalInfo);
|
||||
float lon = Convert.ToSingle(xmlReader.GetAttribute("lon")!, decimalInfo);
|
||||
tmpAllNodes.TryAdd(id, new OsmNode(id, lat, lon));
|
||||
}
|
||||
else if (xmlReader.Name == "way")
|
||||
{
|
||||
isHighway = false;
|
||||
currentIds.Clear();
|
||||
XmlReader wayReader = xmlReader.ReadSubtree();
|
||||
while (wayReader.Read())
|
||||
{
|
||||
if (xmlReader.Name == "tag" && xmlReader.GetAttribute("k")!.Equals("highway"))
|
||||
{
|
||||
isHighway = true;
|
||||
}
|
||||
else if (xmlReader.Name == "nd")
|
||||
{
|
||||
ulong id = Convert.ToUInt64(xmlReader.GetAttribute("ref")!);
|
||||
currentIds.Add(id);
|
||||
}
|
||||
}
|
||||
wayReader.Close();
|
||||
|
||||
foreach (ulong nodeId in currentIds.Where(key => tmpAllNodes.ContainsKey(key)))
|
||||
{
|
||||
BinaryFormatter bFormatter = new BinaryFormatter();
|
||||
if (isHighway)
|
||||
{
|
||||
ulong regionHash = Coordinates.GetRegionHashCode(tmpAllNodes[nodeId].coordinates);
|
||||
FileStream nodesRegionStream;
|
||||
if(regionFileStreams.ContainsKey(regionHash))
|
||||
nodesRegionStream = regionFileStreams[regionHash];
|
||||
else
|
||||
{
|
||||
string regionPath = Path.Combine(outputPath, regionHash.ToString());
|
||||
Directory.CreateDirectory(regionPath);
|
||||
string nodesRegionPath = Path.Combine(regionPath, NodesFileName);
|
||||
nodesRegionStream = new FileStream(nodesRegionPath, FileMode.Create);
|
||||
regionFileStreams.Add(regionHash, nodesRegionStream);
|
||||
}
|
||||
nodeRegions.Add(nodeId, regionHash);
|
||||
|
||||
#pragma warning disable SYSLIB0011 //eheheh
|
||||
bFormatter.Serialize(nodesRegionStream, tmpAllNodes[nodeId]);
|
||||
#pragma warning restore SYSLIB0011
|
||||
}
|
||||
tmpAllNodes.Remove(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
xmlReader.Close();
|
||||
return nodeRegions;
|
||||
}
|
||||
|
||||
private static void ImportWays(XmlReader xmlReader, Dictionary<ulong, ulong> nodeRegions, string outputPath)
|
||||
{
|
||||
List<ulong> currentNodeIds = new();
|
||||
Dictionary<Tag.TagType, dynamic> currentTags = new();
|
||||
Dictionary<ulong, FileStream> regionWaysFileStreams = new();
|
||||
Dictionary<ulong, FileStream> regionTagsFileStreams = new();
|
||||
Dictionary<ulong, HashSet<ulong>> writtenWayTagsInRegion = new();
|
||||
while (xmlReader.ReadToFollowing("way"))
|
||||
{
|
||||
currentNodeIds.Clear();
|
||||
currentTags.Clear();
|
||||
XmlReader wayReader = xmlReader.ReadSubtree();
|
||||
while (wayReader.Read())
|
||||
{
|
||||
currentTags.TryAdd(Tag.TagType.id, Convert.ToUInt64(wayReader.GetAttribute("id")!));
|
||||
if (wayReader.Name == "tag")
|
||||
{
|
||||
HashSet<Tag> pTags = Tag.ConvertToTags(wayReader.GetAttribute("k")!, wayReader.GetAttribute("v")!);
|
||||
if(pTags.Count > 0)
|
||||
foreach (Tag pTag in pTags)
|
||||
currentTags.TryAdd(pTag.key, pTag.value);
|
||||
}
|
||||
else if (wayReader.Name == "nd")
|
||||
{
|
||||
ulong nodeId = Convert.ToUInt64(wayReader.GetAttribute("ref"));
|
||||
currentNodeIds.Add(nodeId);
|
||||
}
|
||||
}
|
||||
wayReader.Close();
|
||||
if (currentTags.ContainsKey(Tag.TagType.highway))
|
||||
{
|
||||
for (int i = 0; i < currentNodeIds.Count - 1; i++)
|
||||
{
|
||||
ulong node1Id = currentNodeIds[i];
|
||||
ulong node2Id = currentNodeIds[i+1];
|
||||
if (nodeRegions.ContainsKey(node1Id) && nodeRegions.ContainsKey(node2Id))
|
||||
{
|
||||
if (currentTags.ContainsKey(Tag.TagType.oneway) && (bool)currentTags[Tag.TagType.oneway])
|
||||
{
|
||||
if (currentTags.ContainsKey(Tag.TagType.forward) && !(bool)currentTags[Tag.TagType.forward])
|
||||
{
|
||||
OsmEdge n21e = new OsmEdge(currentTags[Tag.TagType.id], node2Id, node1Id,
|
||||
nodeRegions[node1Id]);
|
||||
WriteWay(ref regionWaysFileStreams, nodeRegions[node2Id], n21e, outputPath);
|
||||
WriteTags(ref regionTagsFileStreams, ref writtenWayTagsInRegion, nodeRegions[node2Id],
|
||||
currentTags, outputPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
OsmEdge n12e = new OsmEdge(currentTags[Tag.TagType.id], node1Id, node2Id,
|
||||
nodeRegions[node2Id]);
|
||||
WriteWay(ref regionWaysFileStreams, nodeRegions[node1Id], n12e, outputPath);
|
||||
WriteTags(ref regionTagsFileStreams, ref writtenWayTagsInRegion, nodeRegions[node1Id],
|
||||
currentTags, outputPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OsmEdge n12e = new OsmEdge(currentTags[Tag.TagType.id], node1Id, node2Id,
|
||||
nodeRegions[node2Id]);
|
||||
WriteWay(ref regionWaysFileStreams, nodeRegions[node1Id], n12e, outputPath);
|
||||
WriteTags(ref regionTagsFileStreams, ref writtenWayTagsInRegion, nodeRegions[node1Id],
|
||||
currentTags, outputPath);
|
||||
|
||||
OsmEdge n21e = new OsmEdge(currentTags[Tag.TagType.id], node2Id, node1Id,
|
||||
nodeRegions[node1Id]);
|
||||
WriteWay(ref regionWaysFileStreams, nodeRegions[node2Id], n21e, outputPath);
|
||||
WriteTags(ref regionTagsFileStreams, ref writtenWayTagsInRegion, nodeRegions[node2Id],
|
||||
currentTags, outputPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
xmlReader.Close();
|
||||
foreach (FileStream f in regionWaysFileStreams.Values)
|
||||
f.Dispose();
|
||||
foreach(FileStream f in regionTagsFileStreams.Values)
|
||||
f.Dispose();
|
||||
}
|
||||
|
||||
private static void WriteWay(ref Dictionary<ulong, FileStream> regionWaysFileStreams, ulong regionHash, OsmEdge edge, string outputPath)
|
||||
{
|
||||
BinaryFormatter bFormatter = new BinaryFormatter();
|
||||
if (!regionWaysFileStreams.ContainsKey(regionHash))
|
||||
{
|
||||
string waysRegionPath = Path.Combine(outputPath, regionHash.ToString(), WaysFileName);
|
||||
regionWaysFileStreams.Add(regionHash, new FileStream(waysRegionPath, FileMode.OpenOrCreate));
|
||||
}
|
||||
#pragma warning disable SYSLIB0011
|
||||
bFormatter.Serialize(regionWaysFileStreams[regionHash], edge);
|
||||
#pragma warning restore SYSLIB0011
|
||||
|
||||
}
|
||||
|
||||
private static void WriteTags(ref Dictionary<ulong, FileStream> regionTagsFileStreams, ref Dictionary<ulong, HashSet<ulong>> writtenWayTagsInRegion, ulong regionHash, Dictionary<Tag.TagType, dynamic> currentTags, string outputPath)
|
||||
{
|
||||
if (writtenWayTagsInRegion.ContainsKey(regionHash) &&
|
||||
writtenWayTagsInRegion[regionHash].Contains(currentTags[Tag.TagType.id]))
|
||||
return;
|
||||
else if(!writtenWayTagsInRegion.ContainsKey(regionHash))
|
||||
writtenWayTagsInRegion.Add(regionHash, new HashSet<ulong>());
|
||||
|
||||
BinaryFormatter bFormatter = new BinaryFormatter();
|
||||
if (!regionTagsFileStreams.ContainsKey(regionHash))
|
||||
{
|
||||
string tagsRegionPath = Path.Combine(outputPath, regionHash.ToString(), TagsFileName);
|
||||
regionTagsFileStreams.Add(regionHash, new FileStream(tagsRegionPath, FileMode.OpenOrCreate));
|
||||
}
|
||||
|
||||
ulong wayId = currentTags[Tag.TagType.id];
|
||||
writtenWayTagsInRegion[regionHash].Add(wayId);
|
||||
TagManager tm = new TagManager();
|
||||
foreach(KeyValuePair<Tag.TagType, dynamic> kv in currentTags)
|
||||
tm.AddTag(wayId, kv);
|
||||
#pragma warning disable SYSLIB0011
|
||||
bFormatter.Serialize(regionTagsFileStreams[regionHash], tm);
|
||||
#pragma warning restore SYSLIB0011
|
||||
}
|
||||
|
||||
private static void CombineTmpFiles(string folderPath, HashSet<ulong> regionHashes)
|
||||
{
|
||||
foreach (ulong regionId in regionHashes)
|
||||
{
|
||||
ValueTuple<Region, HashSet<OsmEdge>> tmpRegion = LoadRegion(folderPath, regionId);
|
||||
foreach (OsmEdge edge in tmpRegion.Item2)
|
||||
{
|
||||
OsmNode? startNode = tmpRegion.Item1.GetNode(edge.startId);
|
||||
if (startNode is not null)
|
||||
{
|
||||
startNode.edges.Add(edge);
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream tmpRegionFileStream = new FileStream(Path.Join(folderPath, $"{regionId}{RegionsFileName}"), FileMode.Create))
|
||||
{
|
||||
JsonSerializer.Serialize(tmpRegionFileStream, tmpRegion.Item1, typeof(Region), Region.serializerOptions);
|
||||
}
|
||||
Directory.Delete(Path.Join(folderPath, regionId.ToString()), true);
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueTuple<Region, HashSet<OsmEdge>> LoadRegion(string folderPath, ulong regionHash)
|
||||
{
|
||||
Region newRegion = new Region(regionHash);
|
||||
HashSet<OsmEdge> ways = new();
|
||||
BinaryFormatter bFormatter = new BinaryFormatter();
|
||||
if (!Directory.Exists(Path.Join(folderPath, regionHash.ToString())))
|
||||
throw new FileNotFoundException("Region does not exist");
|
||||
|
||||
#pragma warning disable SYSLIB0011
|
||||
string waysPath = Path.Join(folderPath, regionHash.ToString(), RegionConverter.WaysFileName);
|
||||
if(File.Exists(waysPath))
|
||||
using (FileStream wayFileStream = new FileStream(waysPath, FileMode.Open))
|
||||
{
|
||||
while (wayFileStream.Position < wayFileStream.Length)
|
||||
{
|
||||
OsmEdge deserializedEdge = (OsmEdge)bFormatter.Deserialize(wayFileStream);
|
||||
ways.Add(deserializedEdge);
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream nodeFileStream = new FileStream(Path.Join(folderPath, regionHash.ToString(), RegionConverter.NodesFileName), FileMode.Open))
|
||||
{
|
||||
while (nodeFileStream.Position < nodeFileStream.Length)
|
||||
{
|
||||
OsmNode deserializedNode = (OsmNode)bFormatter.Deserialize(nodeFileStream);
|
||||
newRegion.nodes.Add(deserializedNode);
|
||||
}
|
||||
}
|
||||
|
||||
string tagsPath = Path.Join(folderPath, regionHash.ToString(), RegionConverter.TagsFileName);
|
||||
if(File.Exists(tagsPath))
|
||||
using (FileStream tagsFileStream = new FileStream(tagsPath, FileMode.Open))
|
||||
{
|
||||
while (tagsFileStream.Position < tagsFileStream.Length)
|
||||
{
|
||||
TagManager tm = (TagManager)bFormatter.Deserialize(tagsFileStream);
|
||||
ulong id = (ulong)tm.wayTagSets.First()!.Value.First(tag => tag.key == Tag.TagType.id)!.value;
|
||||
foreach(Tag tag in tm.wayTagSets.First()!.Value)
|
||||
newRegion.tagManager.AddTag(id, tag);
|
||||
}
|
||||
}
|
||||
#pragma warning restore SYSLIB0011
|
||||
|
||||
return new ValueTuple<Region, HashSet<OsmEdge>>(newRegion, ways);
|
||||
}
|
||||
}
|
175
Server/Server.cs
175
Server/Server.cs
@ -1,14 +1,183 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Text.Json;
|
||||
using OSMDatastructure;
|
||||
using OSMDatastructure.Graph;
|
||||
using Pathfinding;
|
||||
using RenderPath;
|
||||
using Region = OSMDatastructure.Region;
|
||||
|
||||
namespace Server;
|
||||
|
||||
public class Server
|
||||
{
|
||||
|
||||
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
//XmlImporter.Split("/home/glax/Downloads/oberbayern-latest.osm", "/home/glax/Downloads/oberbayern-latest");
|
||||
//Region r = LoadRegion("/home/glax/Downloads/bayern-latest", new Coordinates(47.890f,12.56f));
|
||||
Pathfinder.CustomAStar("/home/glax/Downloads/oberbayern-latest", new Coordinates(48.243351f, 11.640417f), new Coordinates(48.25239f, 11.53272f));
|
||||
ConsoleWriter newConsole = new();
|
||||
Console.SetOut(newConsole);
|
||||
Console.SetError(newConsole);
|
||||
|
||||
string workingDir = "D:/stuttgart-regbez-latest";
|
||||
|
||||
//RegionConverter.ConvertXMLToRegions("D:/stuttgart-regbez-latest.osm", "D:/stuttgart-regbez-latest");
|
||||
//RegionConverter.ConvertXMLToRegions("D:/map.osm", "D:/map");
|
||||
//RegionConverter.ConvertXMLToRegions("D:/germany-latest.osm", "D:/germany-latest");
|
||||
|
||||
Coordinates start = new (48.7933798f, 9.8275859f);
|
||||
Coordinates finish = new (48.795918f, 9.021618f);
|
||||
|
||||
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");
|
||||
|
||||
/*
|
||||
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, 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}");
|
||||
|
||||
string fileName = DateTime.Now.ToFileTime().ToString();
|
||||
|
||||
string resultFileName = $"{new DirectoryInfo(workingDir).Name}-{fileName}.result";
|
||||
result.SaveResult(Path.Join(parentFolder, resultFileName));
|
||||
|
||||
string renderFileName = $"{new DirectoryInfo(workingDir).Name}-{fileName}.render.png";
|
||||
|
||||
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);
|
||||
*/
|
||||
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();
|
||||
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)}");
|
||||
}
|
||||
|
||||
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;
|
||||
float maxLat = c1.latitude > c2.latitude ? c1.latitude : c2.latitude;
|
||||
float maxLon = c1.longitude > c2.longitude ? c1.longitude : c2.longitude;
|
||||
|
||||
RegionManager allRegions = new(workingDir);
|
||||
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("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 = LoadRegions(workingDir, start, finish);
|
||||
|
||||
Queue<Thread> calcThreads = new();
|
||||
|
||||
Bounds bounds = JsonSerializer.Deserialize<Bounds>(new FileStream(@"D:\bounds", FileMode.Open))!;
|
||||
|
||||
|
||||
for (double extraTime = 1.5; extraTime >= 1; extraTime -= 0.25)
|
||||
{
|
||||
for (double roadFactor = 0.05; roadFactor < 5; roadFactor += 0.05)
|
||||
{
|
||||
double road = roadFactor;
|
||||
double time = extraTime;
|
||||
calcThreads.Enqueue(new Thread(() =>
|
||||
{
|
||||
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}");
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
int totalTasks = calcThreads.Count;
|
||||
int completedTasks = 0;
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
HashSet<Thread> runningThreads = new();
|
||||
Console.WriteLine($"Running {threads} Threads on {totalTasks} Tasks.");
|
||||
while (calcThreads.Count > 0 || runningThreads.Count > 0)
|
||||
{
|
||||
while (runningThreads.Count < threads && calcThreads.Count > 0)
|
||||
{
|
||||
Thread t = calcThreads.Dequeue();
|
||||
runningThreads.Add(t);
|
||||
t.Start();
|
||||
}
|
||||
|
||||
int newCompletedTasks = runningThreads.RemoveWhere(thread => !thread.IsAlive);
|
||||
completedTasks += newCompletedTasks;
|
||||
if (newCompletedTasks > 0)
|
||||
{
|
||||
TimeSpan elapsedTime = DateTime.Now - startTime;
|
||||
Console.WriteLine($"To calculate: {calcThreads.Count}(+{runningThreads.Count} running)/{totalTasks} Time Average: {(elapsedTime/completedTasks)} Elapsed: {elapsedTime} Remaining: {(elapsedTime/completedTasks*calcThreads.Count)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,12 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Pathfinding\Pathfinding.csproj" />
|
||||
<ProjectReference Include="..\RenderPath\RenderPath.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageReference Include="System.Runtime.Serialization.Json" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Reference in New Issue
Block a user