feat: Voeg routeplanning aan frontend toe

This commit is contained in:
2024-07-02 12:18:49 +02:00
parent c7bf314800
commit 0f08868120
15 changed files with 7125 additions and 8696 deletions

View File

@@ -7,7 +7,20 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="79f184c3-e88e-45be-9116-5fa813562754" name="Changes" comment="" />
<list default="true" id="79f184c3-e88e-45be-9116-5fa813562754" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/../CoveragePathPlanner/bin/Debug/net8.0/CoveragePathPlanner.dll" beforeDir="false" afterPath="$PROJECT_DIR$/../CoveragePathPlanner/bin/Debug/net8.0/CoveragePathPlanner.dll" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../CoveragePathPlanner/bin/Debug/net8.0/CoveragePathPlanner.pdb" beforeDir="false" afterPath="$PROJECT_DIR$/../CoveragePathPlanner/bin/Debug/net8.0/CoveragePathPlanner.pdb" 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$/LibParse/bin/Debug/net8.0/LibParse.dll" beforeDir="false" afterPath="$PROJECT_DIR$/LibParse/bin/Debug/net8.0/LibParse.dll" afterDir="false" />
<change beforePath="$PROJECT_DIR$/LibParse/bin/Debug/net8.0/LibParse.pdb" beforeDir="false" afterPath="$PROJECT_DIR$/LibParse/bin/Debug/net8.0/LibParse.pdb" afterDir="false" />
<change beforePath="$PROJECT_DIR$/LibServer/bin/Debug/net8.0/LibServer.dll" beforeDir="false" afterPath="$PROJECT_DIR$/LibServer/bin/Debug/net8.0/LibServer.dll" afterDir="false" />
<change beforePath="$PROJECT_DIR$/LibServer/bin/Debug/net8.0/LibServer.pdb" beforeDir="false" afterPath="$PROJECT_DIR$/LibServer/bin/Debug/net8.0/LibServer.pdb" afterDir="false" />
<change beforePath="$PROJECT_DIR$/RobotControlServer/JsonClasses/Database.cs" beforeDir="false" afterPath="$PROJECT_DIR$/RobotControlServer/JsonClasses/Database.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/RobotControlServer/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/RobotControlServer/Program.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/RobotControlServer/Routes/RoutePlan.cs" beforeDir="false" afterPath="$PROJECT_DIR$/RobotControlServer/Routes/RoutePlan.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../Web/pnpm-lock.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/../Web/pnpm-lock.yaml" 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" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -119,6 +132,8 @@
<workItem from="1718443610193" duration="8513000" />
<workItem from="1719219142765" duration="1400000" />
<workItem from="1719253641573" duration="1910000" />
<workItem from="1719827237918" duration="3979000" />
<workItem from="1719913000122" duration="2097000" />
</task>
<servers />
</component>

View File

@@ -16,3 +16,10 @@ public class RowData {
public string Name;
public DateTime Date;
}
public class PathData {
public string Id;
public string Objects;
public int Version;
public string Name;
public DateTime Date;
}

View File

@@ -14,7 +14,7 @@ internal static class Program {
{ "database", new RouteDatabase(cassandraSession) },
{ "database/metadata", new RouteMetadata(cassandraSession) },
{ "database/path", new RoutePath(cassandraSession) },
{ "database/path/plan", new RoutePlan() },
{ "database/path/plan", new RoutePlan(cassandraSession) },
{ "roomba/control", new RouteControl() }
};

View File

@@ -136,7 +136,7 @@ public class RoutePath(ISession cassandraSession): IRoute {
private string RowsToString(RowSet rowSet) {
return rowSet.Select(row => new RowData {
Id = row.GetValue<Guid>("id").ToString(),
Objects = $"[{row.GetValue<string>("path")}]".FromJson<int[][]>()
Objects = row.GetValue<string>("path").FromJson<int[][]>(),
}).ToList().ToJson();
}
}

View File

@@ -8,20 +8,31 @@ using System.Drawing;
namespace RobotControlServer.Routes;
public class RoutePlan: IRoute {
public class RoutePlan(ISession cassandraSession): IRoute {
/// <summary>
/// POST request for the database/path/plan route, generate a new path
/// from a certain map of objects.
/// </summary>
public HttpResponse Post(HttpRequest request) {
public HttpResponse Get(HttpRequest request) {
try {
// Parse request body to Data object, give error if non-nullable fields
// are null.
string id;
var parsedBody = request.Body?.FromJson<Data>();
if (parsedBody?.objects == null) {
throw new Exception("objectData is null");
if (!request.QueryString.TryGetValue("id", out id)) {
throw new Exception("id is null");
}
// ParsedBody?.objects = [[0,1],[0,2],...]
var selectStatement =
cassandraSession.Prepare(@"SELECT * FROM Roommapper.Maps WHERE Id = ?;").Bind(Guid.Parse(id));
var rowSet = cassandraSession.Execute(selectStatement);
if (rowSet.IsExhausted()) {
throw new Exception("No map found with the given id");
}
var map = rowSet.First();
// New instance of CPP with a 500x500 grid || where 0,0 = top-left and 499, 499 = bottom-right
CoveragePathPlanner planner = new CoveragePathPlanner(500, 500);
@@ -30,7 +41,7 @@ public class RoutePlan: IRoute {
List<Point> obstacles = new List<Point>();
// Add obstacle cells
foreach (var obj in $"[{parsedBody?.objects}]".FromJson<int[][]>())
foreach (var obj in $"[{map.GetValue<string>("objects")}]".FromJson<int[][]>()) // $"[{parsedBody?.objects}]".FromJson<int[][]>()
{
obstacles.Add(new Point(obj[0], obj[1])); // Get x and y for every coordinate in the parsedbody array
}
@@ -43,10 +54,26 @@ public class RoutePlan: IRoute {
}
// Return success message with the path
return new HttpResponse($"{{\"message\":\"success\",\"path\":{points.ToJson()}}}");
var insertStatement = cassandraSession.Prepare(@"
INSERT INTO Roommapper.Routes(Id, path)
VALUES (?, ?);
").Bind(Guid.Parse(id), points.ToJson());
cassandraSession.Execute(insertStatement);
// Return success message with the UUID of the inserted row
return new HttpResponse($"{{\"message\":\"success\",\"id\":\"{id}\"}}");
// return new HttpResponse($"{{\"message\":\"success\",\"path\":{points.ToJson()}}}");
} catch (Exception ex) {
// Return error message if failed for any reason
return new HttpResponse($"{{\"message\": \"{ex.Message.Replace("\"", "\\\"")}\"}}", 400);
}
}
public HttpResponse Options(HttpRequest request) {
var response = new HttpResponse("{\"message\": \"options\"}");
response.Headers.Add("Allow", "GET, POST, OPTIONS");
return response;
}
}

15713
src/Web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -60,8 +60,8 @@ const App = () => {
<Controlfield />
<div style={{ 'marginLeft': '50px' }}>
<MapCanvas
width={600}
height={600}
width={500}
height={500}
onError={Controlfield.handleError}
className='map-canvas'
/>

View File

@@ -12,11 +12,14 @@ export default class MapCanvas extends React.Component {
// Due to `draw`'s asynchronous behavior, we need to bind `this` to the
// handler functionin order for it to be able to be used inside of `render`.
this.handleSubmit = this.handleSubmit.bind(this);
this.handlePlan = this.handlePlan.bind(this);
// Get a reference to the canvas element so we can draw on it
this.canvasRef = React.createRef();
this.state = {
inputValue: '',
searchOption: 'name',
currentMapId: undefined,
currentMap: [],
canvasWidth: props.width || this.defaultWidth,
canvasHeight: props.height || this.defaultHeight,
};
@@ -26,6 +29,10 @@ export default class MapCanvas extends React.Component {
await this.draw();
}
async handlePlan() {
await this.HandlePlan();
}
// This is a lifecycle method that is called after the component has been
// rendered to the DOM. This is where we will draw the initial state
// on the canvas.
@@ -42,9 +49,13 @@ export default class MapCanvas extends React.Component {
// Combine all found sets into a singular array.
const map = { 'id': data.length < 2 ? data[0].Id : undefined, 'points': [] };
if (!Array.isArray(data) || data.length < 1) return map;
console.log(data);
if (data.length < 1) return map;
// if (typeof data[0].Objects === 'string') return { id: data[0].Id, points: JSON.parse(data[0].Objects)};
data.forEach((set) => set.Objects.forEach((coord) => map.points.push(coord)));
this.setState({ currentMapId: data[0].Id, currentMap: data[0].Objects });
return map;
} catch (ex) {
return;
@@ -71,6 +82,15 @@ export default class MapCanvas extends React.Component {
return;
}
if (route || route.points.length > 0) {
ctx.fillStyle = "#800000";
route.points.forEach(point => {
ctx.beginPath();
ctx.arc(point[1], point[0], 1, 0, 2 * Math.PI);
ctx.fill();
});
}
// Loop trough all coordinates, draw a dot at each point to form a top-down
// view of the objects.
ctx.fillStyle = "#000";
@@ -79,13 +99,13 @@ export default class MapCanvas extends React.Component {
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();
});
async HandlePlan() {
if (this.state.currentMap.length < 1) return;
const url = `${API_ENDPOINT}/database/path/plan?id=${this.state.currentMapId}`;
const data = await (await fetch(url)).json();
console.log(data);
}
render() {
@@ -110,6 +130,7 @@ export default class MapCanvas extends React.Component {
disabled={searchOption === 'all'}
/>
<Button type="default" htmlType="submit">Update Map</Button>
<Button type="default" onClick={this.handlePlan}>Plan</Button>
</Form>
<div>
<canvas