This commit is contained in:
zwarenelle
2024-06-13 21:22:23 +02:00
23 changed files with 356 additions and 223 deletions

View File

@@ -1,136 +1,136 @@
using System;
using System.Collections.Generic;
using System.Drawing;
public class CoveragePathPlanner
{
private int _width;
private int _height;
private List<Cell> _cells;
private int _width;
private int _height;
private List<Cell> _cells;
public CoveragePathPlanner(int width, int height)
public CoveragePathPlanner(int width, int height)
{
_width = width;
_height = height;
_cells = new List<Cell>();
for (int x = 0; x < width; x++)
{
_width = width;
_height = height;
_cells = new List<Cell>();
for (int y = 0; y < height; y++)
{
_cells.Add(new Cell { X = x, Y = y, IsObstacle = false, IsVisited = false, Cost = double.MaxValue });
}
}
}
for (int x = 0; x < width; x++)
public List<Point> PlanPath(List<Obstacle> obstacles)
{
// Add obstacles to the grid
foreach (Obstacle obstacle in obstacles)
{
foreach (Cell cell in _cells)
{
if (cell.IsWithinObstacle(obstacle))
{
for (int y = 0; y < height; y++)
{
_cells.Add(new Cell { X = x, Y = y, IsObstacle = false, IsVisited = false, Cost = double.MaxValue });
}
cell.IsObstacle = true;
}
}
}
public List<Point> PlanPath(List<Obstacle> obstacles)
// Initialize the start cell
Cell startCell = _cells[0];
startCell.Cost = 0;
// Create a priority queue to hold the cells to be processed
var queue = new PriorityQueue<Cell, double>();
queue.Enqueue(startCell, 0);
while (queue.Count > 0)
{
// Add obstacles to the grid
foreach (Obstacle obstacle in obstacles)
// Dequeue the cell with the lowest cost
Cell currentCell = queue.Dequeue();
// If the cell is the goal, construct the path
if (currentCell.X == _width - 1 && currentCell.Y == _height - 1)
{
List<Point> path = new List<Point>();
while (currentCell != null)
{
for (int x = obstacle.X; x < obstacle.X + obstacle.Width; x++)
{
for (int y = obstacle.Y; y < obstacle.Y + obstacle.Height; y++)
{
_cells[x + y * _width].IsObstacle = true;
}
}
path.Add(new Point(currentCell.X, currentCell.Y));
currentCell = currentCell.Parent;
}
return path;
}
// Initialize the start cell
Cell startCell = _cells[0];
startCell.Cost = 0;
// Mark the cell as visited
currentCell.IsVisited = true;
// Create a priority queue to hold the cells to be processed
var queue = new PriorityQueue<Cell, double>();
queue.Enqueue(startCell, 0);
while (queue.Count > 0)
// Explore the neighbors
foreach (Cell neighbor in currentCell.GetNeighbors(_cells))
{
if (!neighbor.IsVisited && !neighbor.IsObstacle)
{
// Dequeue the cell with the lowest cost
Cell currentCell = queue.Dequeue();
// If the cell is the goal, construct the path
if (currentCell.X == _width - 1 && currentCell.Y == _height - 1)
{
List<Point> path = new List<Point>();
while (currentCell != null)
{
path.Add(new Point(currentCell.X, currentCell.Y));
currentCell = currentCell.Parent;
}
return path;
}
// Mark the cell as visited
currentCell.IsVisited = true;
// Explore the neighbors
foreach (Cell neighbor in GetNeighbors(currentCell))
{
if (!neighbor.IsVisited && !neighbor.IsObstacle)
{
// Calculate the cost of the neighbor
double cost = currentCell.Cost + 1;
if (cost < neighbor.Cost)
{
neighbor.Cost = cost;
neighbor.Parent = currentCell;
queue.Enqueue(neighbor, cost + Heuristic(neighbor));
}
}
}
// Calculate the cost of the neighbor
double cost = currentCell.Cost + 1;
if (cost < neighbor.Cost)
{
neighbor.Cost = cost;
neighbor.Parent = currentCell;
queue.Enqueue(neighbor, cost + currentCell.Heuristic(_width, _height));
}
}
// If no path is found, return an empty list
return new List<Point>();
}
private List<Cell> GetNeighbors(Cell cell)
{
List<Cell> neighbors = new List<Cell>();
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
if (x == 0 && y == 0) continue;
int neighborX = cell.X + x;
int neighborY = cell.Y + y;
if (neighborX >= 0 && neighborX < _width && neighborY >= 0 && neighborY < _height)
{
neighbors.Add(_cells[neighborX + neighborY * _width]);
}
}
}
return neighbors;
}
private double Heuristic(Cell cell)
{
// Manhattan distance heuristic
return Math.Abs(cell.X - (_width - 1)) + Math.Abs(cell.Y - (_height - 1));
}
}
// If no path is found, return an empty list
return new List<Point>();
}
}
public class Obstacle
{
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
public class Cell
{
public int X { get; set; }
public int Y { get; set; }
public bool IsObstacle { get; set; }
public bool IsVisited { get; set; }
public double Cost { get; set; }
public Cell Parent { get; set; }
public int X { get; set; }
public int Y { get; set; }
public bool IsObstacle { get; set; }
public bool IsVisited { get; set; }
public double Cost { get; set; }
public Cell Parent { get; set; }
public bool IsWithinObstacle(Obstacle obstacle)
{
return X >= obstacle.X && X < obstacle.X + obstacle.Width &&
Y >= obstacle.Y && Y < obstacle.Y + obstacle.Height;
}
public IEnumerable<Cell> GetNeighbors(List<Cell> cells)
{
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
if (x == 0 && y == 0) continue;
int neighborX = X + x;
int neighborY = Y + y;
if (neighborX >= 0 && neighborX < cells.Count / cells[0].Y && neighborY >= 0 && neighborY < cells[0].Y)
{
yield return cells[neighborX + neighborY * cells.Count / cells[0].Y];
}
}
}
}
public double Heuristic(int width, int height)
{
// Manhattan distance heuristic
return Math.Abs(X - (width - 1)) + Math.Abs(Y - (height - 1));
}
}

View File

@@ -4,6 +4,7 @@
<data-source name="roommapper@localhost" uuid="469f6bec-f113-4467-9d39-7ac38558045a">
<database-info product="Cassandra" version="3.0.8" jdbc-version="4.2" driver-name="Cassandra JDBC Driver" driver-version="1.4" dbms="CASSANDRA" exact-version="3.0.8" exact-driver-version="1.4">
<identifier-quote-string>&quot;</identifier-quote-string>
<jdbc-catalog-is-schema>true</jdbc-catalog-is-schema>
</database-info>
<case-sensitivity plain-identifiers="lower" quoted-identifiers="exact" />
<secret-storage>master_key</secret-storage>

View File

@@ -8,33 +8,33 @@
<CanLogin>1</CanLogin>
<SuperRole>1</SuperRole>
</role>
<schema id="3" parent="1" name="system_auth">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.SimpleStrategy&apos;, &apos;replication_factor&apos;: &apos;1&apos;}
</Properties>
</schema>
<schema id="4" parent="1" name="system_schema">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.LocalStrategy&apos;}
</Properties>
</schema>
<schema id="5" parent="1" name="eve">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.SimpleStrategy&apos;, &apos;replication_factor&apos;: &apos;1&apos;}
</Properties>
</schema>
<schema id="6" parent="1" name="roommapper">
<schema id="3" parent="1" name="roommapper">
<Current>1</Current>
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.NetworkTopologyStrategy&apos;, &apos;datacenter1&apos;: &apos;1&apos;}
</Properties>
</schema>
<schema id="7" parent="1" name="system_distributed">
<schema id="4" parent="1" name="system">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.LocalStrategy&apos;}
</Properties>
</schema>
<schema id="5" parent="1" name="system_auth">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.SimpleStrategy&apos;, &apos;replication_factor&apos;: &apos;1&apos;}
</Properties>
</schema>
<schema id="6" parent="1" name="system_distributed">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.SimpleStrategy&apos;, &apos;replication_factor&apos;: &apos;3&apos;}
</Properties>
</schema>
<schema id="8" parent="1" name="system">
<schema id="7" parent="1" name="system_distributed_everywhere">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.EverywhereStrategy&apos;}
</Properties>
</schema>
<schema id="8" parent="1" name="system_schema">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.LocalStrategy&apos;}
</Properties>
@@ -44,53 +44,5 @@ replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.LocalStrategy
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.SimpleStrategy&apos;, &apos;replication_factor&apos;: &apos;2&apos;}
</Properties>
</schema>
<schema id="10" parent="1" name="system_distributed_everywhere">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.EverywhereStrategy&apos;}
</Properties>
</schema>
<schema id="11" parent="1" name="discordlogger">
<Properties>durable_writes:true
replication:{&apos;class&apos;: &apos;org.apache.cassandra.locator.SimpleStrategy&apos;, &apos;replication_factor&apos;: &apos;1&apos;}
</Properties>
</schema>
<table id="12" parent="6" name="maps">
<Properties>caching:{&apos;keys&apos;: &apos;ALL&apos;, &apos;rows_per_partition&apos;: &apos;ALL&apos;}
compression:{&apos;sstable_compression&apos;: &apos;org.apache.cassandra.io.compress.LZ4Compressor&apos;}
bloom_filter_fp_chance:0.01
default_time_to_live:0
speculative_retry:99.0PERCENTILE
gc_grace_seconds:864000
max_index_interval:2048
memtable_flush_period_in_ms:0
min_index_interval:128
read_repair_chance:0
crc_check_chance:1
dclocal_read_repair_chance:0
compaction:{&apos;class&apos;: &apos;SizeTieredCompactionStrategy&apos;}
</Properties>
</table>
<column id="13" parent="12" name="id">
<DasType>uuid|0s</DasType>
<Position>1</Position>
</column>
<column id="14" parent="12" name="version">
<DasType>int|0s</DasType>
<Position>2</Position>
</column>
<column id="15" parent="12" name="date">
<DasType>timestamp|0s</DasType>
<Position>3</Position>
</column>
<column id="16" parent="12" name="objects">
<DasType>text|0s</DasType>
<Position>4</Position>
</column>
<key id="17" parent="12" name="primary key">
<Columns>id
version|ASC
date|ASC
</Columns>
</key>
</database-model>
</dataSource>

View File

@@ -8,12 +8,14 @@
</component>
<component name="ChangeListManager">
<list default="true" id="79f184c3-e88e-45be-9116-5fa813562754" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/.idea.REST API/.idea/dataSources.local.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.REST API/.idea/dataSources.local.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.REST API/.idea/dataSources/469f6bec-f113-4467-9d39-7ac38558045a.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.REST API/.idea/dataSources/469f6bec-f113-4467-9d39-7ac38558045a.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.REST API/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.REST API/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../Web/public/favicon.ico" beforeDir="false" afterPath="$PROJECT_DIR$/../Web/public/favicon.ico" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../Web/public/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/../Web/public/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../Web/src/components/controlField.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/../Web/src/components/controlField.jsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../Web/src/components/layout.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/../Web/src/components/layout.jsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Console/JsonClasses/Database.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Console/JsonClasses/Database.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Console/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Console/Program.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Console/Routes/RouteDatabase.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Console/Routes/RouteDatabase.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Console/Routes/RoutePath.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Console/Routes/RoutePath.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Console/bin/Debug/net8.0/Console.dll" beforeDir="false" afterPath="$PROJECT_DIR$/Console/bin/Debug/net8.0/Console.dll" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Console/bin/Debug/net8.0/Console.pdb" beforeDir="false" afterPath="$PROJECT_DIR$/Console/bin/Debug/net8.0/Console.pdb" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../Web/src/components/mapCanvas.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/../Web/src/components/mapCanvas.jsx" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
@@ -42,34 +44,34 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
".NET Project.Console.executor": "Run",
"ASKED_ADD_EXTERNAL_FILES": "true",
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"d452b4e4-aa15-4fa9-847f-33d4d7fab23d.executor": "Run",
"git-widget-placeholder": "master",
"ignore.virus.scanning.warn.message": "true",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "HotReloadSettingsPage",
"vue.rearranger.settings.migration": "true"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;.NET Project.Console.executor&quot;: &quot;Run&quot;,
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;d452b4e4-aa15-4fa9-847f-33d4d7fab23d.executor&quot;: &quot;Run&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;HotReloadSettingsPage&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
"keyToStringList": {
"DatabaseDriversLRU": [
"cassandra"
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;cassandra&quot;
],
"rider.external.source.directories": [
"/home/wessel/.config/JetBrains/Rider2024.1/resharper-host/DecompilerCache",
"/home/wessel/.config/JetBrains/Rider2024.1/resharper-host/SourcesCache",
"/home/wessel/.local/share/Symbols/src"
&quot;rider.external.source.directories&quot;: [
&quot;/home/wessel/.config/JetBrains/Rider2024.1/resharper-host/DecompilerCache&quot;,
&quot;/home/wessel/.config/JetBrains/Rider2024.1/resharper-host/SourcesCache&quot;,
&quot;/home/wessel/.local/share/Symbols/src&quot;
]
}
}]]></component>
}</component>
<component name="RunManager">
<configuration name="Console" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/Console/bin/Debug/net8.0/Console.exe" />
@@ -118,6 +120,10 @@
<workItem from="1717492595765" duration="6218000" />
<workItem from="1717584416884" duration="5757000" />
<workItem from="1717602912720" duration="1420000" />
<workItem from="1717668473610" duration="971000" />
<workItem from="1717670832494" duration="901000" />
<workItem from="1717673464254" duration="1428000" />
<workItem from="1718093417281" duration="4387000" />
</task>
<servers />
</component>

View File

@@ -6,11 +6,13 @@ public class Data {
public int version;
public string date;
public string id;
public string name;
}
public class RowData {
public string Id;
public int[][] Objects;
public int Version;
public string Name;
public DateTime Date;
}

View File

@@ -19,6 +19,7 @@ internal static class Program {
{ "", new Root() },
{ "database", new RouteDatabase(cassandraSession) },
{ "database/metadata", new RouteMetadata(cassandraSession) },
{ "database/path", new RoutePath(cassandraSession) },
{ "roomba/control", new RouteControl() }
};
@@ -39,10 +40,18 @@ internal static class Program {
session.Execute(@"
CREATE TABLE IF NOT EXISTS Roommapper.Maps(
Id uuid,
Objects text,
Version int,
Name text,
Objects text,
Date timestamp,
PRIMARY KEY (Id,Version,Date)
PRIMARY KEY (Id,Name)
)");
session.Execute(@"
CREATE TABLE IF NOT EXISTS Roommapper.Routes(
Id uuid,
Path text,
PRIMARY KEY (Id)
)");
}
}

View File

@@ -12,13 +12,13 @@ public class RouteDatabase(ISession cassandraSession): IRoute {
/// </summary>
public HttpResponse Get(HttpRequest request) {
try {
// Prepare the select statement based on the given criteria
// If no criteria is given, throw an exception.
// Using a prepare due to the potential of SQL injection when accepting
// any form of user data.
var queries = new Dictionary<string, Func<string, BoundStatement>> {
["id"] = id => cassandraSession.Prepare(@"SELECT * FROM Roommapper.Maps WHERE Id = ?;").Bind(Guid.Parse(id)),
["name"] = name => cassandraSession.Prepare(@"SELECT * FROM Roommapper.Maps WHERE Name = ?;").Bind(name),
["date"] = date => cassandraSession.Prepare(@"SELECT * FROM roommapper.maps WHERE Date = ?;").Bind(DateTime.Parse(date)),
["version"] = version => cassandraSession.Prepare(@"SELECT * FROM roommapper.maps WHERE Version = ?;").Bind(int.Parse(version)),
["all"] = _ => cassandraSession.Prepare(@"SELECT * FROM roommapper.maps;").Bind()
@@ -29,7 +29,7 @@ public class RouteDatabase(ISession cassandraSession): IRoute {
.Select(query => query.Value(request.QueryString[query.Key]))
.FirstOrDefault();
if (selectStatement == null) throw new Exception("No valid search criteria given, give one of (id, date, version).");
if (selectStatement == null) throw new Exception("No valid search criteria given, give one of (id, date, version, name).");
// Execute the query and return all rows in an array
@@ -65,9 +65,9 @@ public class RouteDatabase(ISession cassandraSession): IRoute {
var uuid = Guid.NewGuid();
var insertStatement = cassandraSession.Prepare(@"
INSERT INTO Roommapper.Maps(Id, Date, Version, Objects)
VALUES (?, toTimeStamp(now()), ?, ?);
").Bind(uuid, 1, parsedBody.objects);
INSERT INTO Roommapper.Maps(Id, Name, Date, Version, Objects)
VALUES (?, ?, toTimeStamp(now()), ?, ?);
").Bind(uuid, parsedBody.name, 1, parsedBody.objects);
cassandraSession.Execute(insertStatement);
@@ -126,7 +126,8 @@ public class RouteDatabase(ISession cassandraSession): IRoute {
Id = row.GetValue<Guid>("id").ToString(),
Objects = $"[{row.GetValue<string>("objects")}]".FromJson<int[][]>(),
Version = row.GetValue<int>("version"),
Date = row.GetValue<DateTime>("date")
Date = row.GetValue<DateTime>("date"),
Name = row.GetValue<string>("name")
}).ToList().ToJson();
}
}

View File

@@ -0,0 +1,142 @@
using Cassandra;
using LibParse.Json;
using LibServer.Http;
using LibServer.Router;
using Console.JsonClasses;
namespace Console.Routes;
public class RoutePath(ISession cassandraSession): IRoute {
/// <summary>
/// GET request for the database/path route, return a path of a map based on given criteria.
/// </summary>
public HttpResponse Get(HttpRequest request) {
try {
// Prepare the select statement based on the given criteria
// If no criteria is given, throw an exception.
// Using a prepare due to the potential of SQL injection when accepting
// any form of user data.
var queries = new Dictionary<string, Func<string, BoundStatement>> {
["id"] = id => cassandraSession.Prepare(@"SELECT * FROM Roommapper.Routes WHERE Id = ?;").Bind(Guid.Parse(id)),
["name"] = name => cassandraSession.Prepare(@"SELECT * FROM Roommapper.Routes WHERE Id = ?;").Bind(name)
};
var selectStatement = queries
.Where(query => request.QueryString.ContainsKey(query.Key))
.Select(query => query.Value(request.QueryString[query.Key]))
.FirstOrDefault();
if (selectStatement == null) throw new Exception("No valid search criteria given, give one of (id, name).");
// Execute the query and return all rows in an array
var rowSet = cassandraSession.Execute(selectStatement);
// Create a list to hold the row data, convert it to a JSON string
var rows = RowsToString(rowSet);
// Return the JSON string to the user
return new HttpResponse(rows);
} catch (Exception ex) {
// Return error message if failed for any reason
return new HttpResponse($"{{\"message\": \"{ex.Message.Replace("\"", "\\\"")}\"}}", 400);
}
}
/// <summary>
/// POST request for the database/path route, insert a new path containing
/// routing data into the database.
/// </summary>
public HttpResponse Post(HttpRequest request) {
try {
// Parse request body to Data object, give error if non-nullable fields
// are null.
var parsedBody = request.Body?.FromJson<Data>();
if (parsedBody?.objects == null) {
throw new Exception("objectData is null");
}
if (parsedBody?.id == null) {
throw new Exception("id is null");
}
var selectStatement =
parsedBody?.id != null ? cassandraSession.Prepare(@"SELECT * FROM Roommapper.Maps WHERE Id = ?;")
.Bind(Guid.Parse(parsedBody.id)) :
throw new Exception("No valid search criteria given, give one of (id).");
// Execute the query and return all rows in an array
var rowSet = cassandraSession.Execute(selectStatement);
if (rowSet.IsExhausted()) {
throw new Exception("No matching map found with the given criteria.");
}
// Prepare the insert statement, bind the values, and execute the query
// Using a prepare due to the potential of SQL injection when accepting
// any form of user data.
var uuid = Guid.Parse(parsedBody.id);
var insertStatement = cassandraSession.Prepare(@"
INSERT INTO Roommapper.Routes(Id, path)
VALUES (?, ?);
").Bind(uuid, parsedBody.objects);
cassandraSession.Execute(insertStatement);
// Return success message with the UUID of the inserted row
return new HttpResponse($"{{\"message\":\"success\",\"id\":\"{uuid}\"}}");
}
catch (Exception ex) {
// Return error message if failed for any reason
return new HttpResponse($"{{\"message\": \"{ex.Message.Replace("\"", "\\\"")}\"}}", 400);
}
}
/// <summary>
/// Deletes a given row
/// </summary>
public HttpResponse Delete(HttpRequest request) {
try {
var parsedBody = request.Body?.FromJson<Data>();
// Prepare the select statement based on the given criteria
// If no criteria is given, throw an exception.
// Using a prepare due to the potential of SQL injection when accepting
// any form of user data.
var selectStatement =
parsedBody?.id != null ? cassandraSession.Prepare(@"SELECT * FROM Roommapper.Routes WHERE Id = ?;")
.Bind(Guid.Parse(parsedBody.id)) :
throw new Exception("No valid search criteria given, give one of (id).");
// Execute the query and return all rows in an array
var rowSet = cassandraSession.Execute(selectStatement);
if (rowSet.IsExhausted()) {
throw new Exception("No rows found with the given criteria.");
}
// Prepare and execute delete statement
var deleteStatement = cassandraSession.Prepare(@"DELETE FROM Roommapper.Routes WHERE Id = ?;")
.Bind(Guid.Parse(parsedBody.id));
cassandraSession.Execute(deleteStatement);
return new HttpResponse($"{{\"message\":\"success\",\"entry\":{RowsToString(rowSet)}}}");
} catch (Exception ex) {
return new HttpResponse($"{{\"message\": \"{ex.Message.Replace("\"", "\\\"")}\"}}", 400);
}
}
/// <summary>
/// Casts a set of `RowSet` into a valid JSON string.
/// </summary>
/// <param name="rowSet">The `RowSet` to cast.</param>
/// <returns>A valid JSON string formed from `rowSet`.</returns>
private string RowsToString(RowSet rowSet) {
return rowSet.Select(row => new RowData {
Id = row.GetValue<Guid>("id").ToString(),
Objects = $"[{row.GetValue<string>("path")}]".FromJson<int[][]>()
}).ToList().ToJson();
}
}

View File

@@ -47,7 +47,6 @@ public class HttpRequest {
catch { return -1; }
}
var querySplit = Route.Split("?");
if (querySplit.Length == 1) return 0;

View File

@@ -1,5 +1,5 @@
<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:String x:Key="/Default/CodeInspection/PencilsConfiguration/ActualSeverity/@EntryValue">INFO</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=d452b4e4_002Daa15_002D4fa9_002D847f_002D33d4d7fab23d/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="ParseJsonToAnonymousObject" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;Project Location="C:\Users\wesse\OneDrive - Hogeschool Inholland\roommapper\src\Server\Tests" Presentation="&amp;lt;Tests&amp;gt;" /&gt;&#xD;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=d452b4e4_002Daa15_002D4fa9_002D847f_002D33d4d7fab23d/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="ParseJsonToAnonymousObject" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Project Location="C:\Users\wesse\OneDrive - Hogeschool Inholland\roommapper\src\Server\Tests" Presentation="&amp;lt;Tests&amp;gt;" /&gt;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>

View File

@@ -15,8 +15,8 @@ export default class MapCanvas extends React.Component {
// Get a reference to the canvas element so we can draw on it
this.canvasRef = React.createRef();
this.state = {
inputValue: '8a97354d-d4ac-43c6-8b32-528cc24e3ede',
searchOption: 'id',
inputValue: '',
searchOption: 'name',
canvasWidth: props.width || this.defaultWidth,
canvasHeight: props.height || this.defaultHeight,
};
@@ -33,16 +33,18 @@ export default class MapCanvas extends React.Component {
this.draw();
}
async getLinePoints() {
async getLinePoints(endpoint = '', altKey, altVal) {
try {
const { searchOption, inputValue } = this.state;
const url = `${API_ENDPOINT}/database?${searchOption}=${inputValue}`;
const key = altKey || this.state.searchOption;
const val = altVal || this.state.inputValue;
const url = `${API_ENDPOINT}/database/${endpoint}?${key}=${val}`;
const data = await (await fetch(url)).json();
// Combine all found sets into a singular array.
const map = [];
const map = { 'id': data.length < 2 ? data[0].Id : undefined, 'points': [] };
if (!Array.isArray(data) || data.length < 1) return map;
data.forEach((set) => set.Objects.forEach((coord) => map.push(coord)));
data.forEach((set) => set.Objects.forEach((coord) => map.points.push(coord)));
return map;
} catch (ex) {
@@ -55,7 +57,8 @@ export default class MapCanvas extends React.Component {
// Get the 2D context from the canvas element
const ctx = this.canvasRef.current.getContext('2d');
// Get the points to draw from the server
const points = await this.getLinePoints();
const map = await this.getLinePoints();
const route = await this.getLinePoints('path', 'id', map ? map.id : '');
// Set the font for the text we will draw on the canvas
ctx.font = "24px Comic Sans MS";
@@ -64,14 +67,22 @@ export default class MapCanvas extends React.Component {
ctx.clearRect(0, 0, this.state.canvasWidth, this.state.canvasHeight);
// If there are no points, display a message and return
if (!points || points.length < 1) {
if (!map || map.points.length < 1) {
ctx.fillText("No valid entry found.", 10, this.state.canvasHeight / 2);
return;
}
// Loop trough all coordinates, draw a dot at each point to form a top-down
// view of the objects.
points.forEach(point => {
ctx.fillStyle = "#000";
map.points.forEach(point => {
ctx.beginPath();
ctx.arc(point[1], point[0], 1, 0, 2 * Math.PI);
ctx.fill();
});
ctx.fillStyle = "#800000";
route.points.forEach(point => {
ctx.beginPath();
ctx.arc(point[1], point[0], 1, 0, 2 * Math.PI);
ctx.fill();
@@ -89,9 +100,8 @@ export default class MapCanvas extends React.Component {
value={searchOption}
onChange={(val) => this.setState({ searchOption: val })}
>
<Select value="name">Name</Select>
<Select value="id">ID</Select>
<Select value="version">Version</Select>
<Select value="date">Date</Select>
<Select value="all">All</Select>
</Select>
<Input