Spaces:
Sleeping
Sleeping
| 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; | |
| } |