order-picking-python / static /warehouse-api.js
blackopsrepl's picture
Upload 31 files
e40294e verified
const LEFT = 'LEFT';
const RIGHT = 'RIGHT';
const WAREHOUSE_COLUMNS = ['A', 'B', 'C', 'D', 'E'];
const WAREHOUSE_ROWS = ['1', '2', '3'];
const WAREHOUSE_PADDING_TOP = 100;
const WAREHOUSE_PADDING_LEFT = 100;
const SHELVING_PADDING = 80;
const SHELVING_WIDTH = 150;
const SHELVING_HEIGHT = 300;
const SHELVING_ROWS = 10;
const SHELVING_LINE_WIDTH = 5;
const SHELVING_STROKE_STYLE = 'green';
const SHELVING_LINE_JOIN = 'bevel';
const TROLLEY_PATH_LINE_WIDTH = 5;
const TROLLEY_PATH_LINE_JOIN = 'round';
const SHELVINGS_MAP = new Map();
function shelvingId(i, j) {
return '(' + i + ',' + j + ')';
}
class Point {
x = 0;
y = 0;
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ShelvingShape {
id;
x = 0;
y = 0;
width = 0;
height = 0;
constructor(id, x, y, width, height) {
this.id = id;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}
class WarehouseLocation {
shelvingId;
side;
row;
constructor(shelvingId, side, row) {
this.shelvingId = shelvingId;
this.side = side;
this.row = row;
}
}
/**
* Returns the WarehouseCanvas.
*/
function getWarehouseCanvasContext() {
return document.getElementById('warehouseCanvas').getContext('2d');
}
/**
* Clears the WarehouseCanvas.
*/
function clearWarehouseCanvas() {
const canvas = document.getElementById('warehouseCanvas');
const ctx = canvas.getContext('2d');
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
const parentWidth = canvas.parentElement.clientWidth;
const parentHeight = canvas.parentElement.clientHeight;
const contentWidth = 2 * WAREHOUSE_PADDING_LEFT + (SHELVING_WIDTH + SHELVING_PADDING) * WAREHOUSE_COLUMNS.length;
const contentHeight = 2 * WAREHOUSE_PADDING_TOP + (SHELVING_HEIGHT + SHELVING_PADDING) * WAREHOUSE_ROWS.length;
const minToFillParent = Math.min(parentWidth / contentWidth, parentHeight / contentHeight);
const xOffset = (parentWidth - (contentWidth * minToFillParent)) / 2;
const yOffset = (parentHeight - (contentHeight * minToFillParent)) / 2;
canvas.width = parentWidth;
canvas.height = parentHeight;
ctx.translate(xOffset, yOffset);
ctx.scale(minToFillParent, minToFillParent);
}
/**
* Draws the WarehouseStructure on the WarehouseCanvas.
*/
function drawWarehouse() {
const ctx = getWarehouseCanvasContext();
let x = WAREHOUSE_PADDING_LEFT;
for (let column = 0; column < WAREHOUSE_COLUMNS.length; column++) {
let y = WAREHOUSE_PADDING_TOP;
for (let row = 0; row < WAREHOUSE_ROWS.length; row++) {
const shelving = new ShelvingShape(shelvingId(WAREHOUSE_COLUMNS[column], WAREHOUSE_ROWS[row]), x, y, SHELVING_WIDTH, SHELVING_HEIGHT);
drawShelving(ctx, shelving);
SHELVINGS_MAP.set(shelving.id, shelving);
y = y + SHELVING_HEIGHT + SHELVING_PADDING;
}
x = x + SHELVING_WIDTH + SHELVING_PADDING;
}
}
/**
* Draws a ShelvingShape on the Warehouse canvas.
*/
function drawShelving(ctx, shelving) {
ctx.strokeStyle = SHELVING_STROKE_STYLE;
ctx.lineJoin = SHELVING_LINE_JOIN;
ctx.lineWidth = SHELVING_LINE_WIDTH;
ctx.strokeRect(shelving.x, shelving.y, shelving.width, shelving.height);
ctx.font = '30px serif';
ctx.textAlign = 'center';
ctx.fillStyle = SHELVING_STROKE_STYLE
const shelvingTextParts = shelving.id.split(',');
ctx.fillText(shelvingTextParts[0].substring(1) + shelvingTextParts[1].substring(0, shelvingTextParts[1].length - 1),
shelving.x + shelving.width / 2, shelving.y + shelving.height / 2);
}
/**
* Draws the path travelled by a Trolley.
*/
function drawTrolleyPath(strokeStyle, warehouseLocations, trolleyIndex, trolleyCount) {
const ctx = getWarehouseCanvasContext();
ctx.lineJoin = TROLLEY_PATH_LINE_JOIN;
ctx.lineWidth = TROLLEY_PATH_LINE_WIDTH;
ctx.strokeStyle = strokeStyle;
drawWarehousePath(warehouseLocations, trolleyIndex, trolleyCount);
}
function drawTrolleyText(strokeStyle, warehouseLocations, trolleyIndex, trolleyCount) {
const ctx = getWarehouseCanvasContext();
ctx.lineJoin = TROLLEY_PATH_LINE_JOIN;
ctx.lineWidth = TROLLEY_PATH_LINE_WIDTH;
ctx.strokeStyle = strokeStyle;
drawTextForTrolley(strokeStyle, warehouseLocations, trolleyIndex, trolleyCount);
}
/**
* Draws a path composed of WarehouseLocations.
*/
function drawWarehousePath(warehouseLocations, trolleyIndex, trolleyCount) {
const ctx = getWarehouseCanvasContext();
const startLocation = warehouseLocations[0];
const startShelving = SHELVINGS_MAP.get(startLocation.shelvingId);
const startPoint = location2Point(startShelving, startLocation.side, startLocation.row, trolleyIndex, trolleyCount);
let lastPoint = startPoint;
let lastShelving = startShelving;
let lastSide = startLocation.side;
let lastRow = startLocation.row;
ctx.beginPath();
ctx.moveTo(startPoint.x, startPoint.y);
ctx.arc(startPoint.x, startPoint.y, 5, 0, 2 * Math.PI);
for (let i = 1; i < warehouseLocations.length; i++) {
const location = warehouseLocations[i];
const shelving = SHELVINGS_MAP.get(location.shelvingId);
const side = location.side;
const row = location.row;
const point = location2Point(shelving, location.side, location.row, trolleyIndex, trolleyCount);
drawWarehousePathBetweenShelves(ctx, trolleyIndex, trolleyCount, lastShelving, lastSide, lastRow, lastPoint,
shelving, side, row, point);
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI);
lastPoint = point;
lastShelving = shelving;
lastSide = side;
lastRow = row;
}
ctx.stroke();
ctx.closePath();
}
function drawTextForTrolley(strokeStyle, warehouseLocations, trolleyIndex, trolleyCount) {
const ctx = getWarehouseCanvasContext();
ctx.fillStyle = strokeStyle;
ctx.strokeStyle = "#000000";
let overlappingOrderTexts = [];
const SHELVING_ROW_HEIGHT = SHELVING_HEIGHT / SHELVING_ROWS;
const TEXT_SEPERATOR_HEIGHT = SHELVING_ROW_HEIGHT * 4;
const SEPERATION_PER_TROLLEY = TEXT_SEPERATOR_HEIGHT / trolleyCount;
for (let i = 0; i < warehouseLocations.length; i++) {
const location = warehouseLocations[i];
const shelving = SHELVINGS_MAP.get(location.shelvingId);
const point = location2Point(shelving, location.side, location.row, trolleyIndex, trolleyCount);
const pointFlooredRow = Math.floor(point.y / TEXT_SEPERATOR_HEIGHT) * TEXT_SEPERATOR_HEIGHT;
point.y = pointFlooredRow + SEPERATION_PER_TROLLEY * trolleyIndex;
addToOverlap(overlappingOrderTexts, i.toString(10), point);
}
for (let i = 0; i < overlappingOrderTexts.length; i++) {
const overlappingOrders = overlappingOrderTexts[i];
const text = '(' + overlappingOrders.orders.join(', ') + ')';
ctx.strokeText(text, overlappingOrders.x, overlappingOrders.y);
ctx.fillText(text, overlappingOrders.x, overlappingOrders.y);
}
}
function addToOverlap(overlappingOrderTexts, orderText, orderPoint) {
const SHELVING_ROW_HEIGHT = SHELVING_HEIGHT / SHELVING_ROWS;
for (let i = 0; i < overlappingOrderTexts.length; i++) {
const overlappingOrderText = overlappingOrderTexts[i];
const distance = Math.abs(orderPoint.x - overlappingOrderText.x) + Math.abs(orderPoint.y - overlappingOrderText.y);
if (distance < 2 * SHELVING_ROW_HEIGHT) {
overlappingOrderText.orders.push(orderText);
return;
}
}
const newOverlappingOrderText = {
orders: [orderText],
x: orderPoint.x,
y: orderPoint.y,
};
overlappingOrderTexts.push(newOverlappingOrderText);
}
/**
* Draw a path around shelves connecting two WarehouseLocations
*/
function drawWarehousePathBetweenShelves(ctx, trolleyIndex, trolleyCount,
startShelving, startSide, startRow, startPoint,
endShelving, endSide, endRow, endPoint) {
ctx.moveTo(startPoint.x, startPoint.y);
if (startShelving === endShelving) {
if (startSide === endSide) {
// Two points on the same shelf and same side
ctx.lineTo(endPoint.x, endPoint.y);
} else {
// Two points on the same shelf but different sides
const isAbove = startRow + endRow < 2*SHELVING_ROWS - startRow - endRow;
const aisleChangeStartPoint = location2AisleLane(startShelving, startSide, isAbove, trolleyIndex, trolleyCount);
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, isAbove, trolleyIndex, trolleyCount);
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y);
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
}
} else if (startShelving.x === endShelving.x) {
if (startSide === endSide) {
// Same Aisle, different rows
ctx.lineTo(endPoint.x, endPoint.y);
} else {
// Different Aisle
if (startShelving.y < endShelving.y) {
// Going up
const aisleChangeStartPoint = location2AisleLane(endShelving, startSide, false, trolleyIndex, trolleyCount);
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, false, trolleyIndex, trolleyCount);
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y);
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
} else {
// Going down
const aisleChangeStartPoint = location2AisleLane(endShelving, startSide, true, trolleyIndex, trolleyCount);
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, true, trolleyIndex, trolleyCount);
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y);
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
}
}
} else if (startShelving.y === endShelving.y) {
const startColumn = shelvingToColumn(startShelving);
const endColumn = shelvingToColumn(endShelving);
if (startSide === LEFT) {
if (endSide === RIGHT && endColumn === startColumn - 1) {
// Same Aisle, different shelving
ctx.lineTo(endPoint.x, startPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
} else {
drawWarehousePathBetweenColumns(ctx, trolleyIndex, trolleyCount,
startShelving, startSide, startRow, startPoint,
endShelving, endSide, endRow, endPoint);
}
} else {
if (endSide === LEFT && endColumn === startColumn + 1) {
// Same Aisle, different shelving
ctx.lineTo(endPoint.x, startPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
} else {
drawWarehousePathBetweenColumns(ctx, trolleyIndex, trolleyCount,
startShelving, startSide, startRow, startPoint,
endShelving, endSide, endRow, endPoint);
}
}
} else {
drawWarehousePathBetweenColumns(ctx, trolleyIndex, trolleyCount,
startShelving, startSide, startRow, startPoint,
endShelving, endSide, endRow, endPoint);
}
}
/**
* Draws a path between shelvings in two different columns
*/
function drawWarehousePathBetweenColumns(ctx, trolleyIndex, trolleyCount,
startShelving, startSide, startRow, startPoint,
endShelving, endSide, endRow, endPoint) {
if (startShelving.y === endShelving.y) {
const isAbove = startRow + endRow < 2*SHELVING_ROWS - startRow - endRow;
const aisleChangeStartPoint = location2AisleLane(startShelving, startSide, isAbove, trolleyIndex, trolleyCount);
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, isAbove, trolleyIndex, trolleyCount);
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y);
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
} else {
const isAbove = endShelving.y < startShelving.y;
const aisleChangeStartPoint = location2AisleLane(startShelving, startSide, isAbove, trolleyIndex, trolleyCount);
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, isAbove, trolleyIndex, trolleyCount);
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y);
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeStartPoint.y);
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
}
}
/**
* Transforms a WarehouseLocation into an absolute Point in the canvas.
*/
function location2Point(shelving, side, row, trolleyIndex, trolleyCount) {
let x;
let y;
if (side === LEFT) {
x = shelving.x - SHELVING_LINE_WIDTH - ((0.5 * SHELVING_PADDING) / trolleyCount) * trolleyIndex;
} else {
x = shelving.x + shelving.width + SHELVING_LINE_WIDTH + ((0.5 * SHELVING_PADDING) / trolleyCount) * trolleyIndex;
}
const rowWidth = shelving.height / SHELVING_ROWS;
y = shelving.y + row * rowWidth;
return new Point(x, y);
}
/**
* Get location for the aisle lane above or below a shelving side
*/
function location2AisleLane(shelving, side, isAbove, trolleyIndex, trolleyCount) {
let x;
let y;
if (side === LEFT) {
x = shelving.x - SHELVING_LINE_WIDTH - ((0.5 * SHELVING_PADDING) / trolleyCount) * trolleyIndex;
} else {
x = shelving.x + SHELVING_LINE_WIDTH + shelving.width + ((0.5 * SHELVING_PADDING) / trolleyCount) * trolleyIndex;
}
if (isAbove) {
y = shelving.y - 0.25 * SHELVING_PADDING - (((0.25 * SHELVING_PADDING) / trolleyCount) * trolleyIndex);
} else {
y = shelving.y + shelving.height + 0.25 * SHELVING_PADDING + (((0.25 * SHELVING_PADDING) / trolleyCount) * trolleyIndex);
}
return new Point(x, y);
}
/**
* Get the column number of a shelving
*/
function shelvingToColumn(shelving) {
const COLUMN_WIDTH = SHELVING_WIDTH + SHELVING_PADDING;
return (shelving.x - WAREHOUSE_PADDING_LEFT) / COLUMN_WIDTH;
}