Step 3: Pallet Position Simulation
The warehouse is designed for storing goods on pallets. Let’s simulate this.
The PalletPosition
class will represent the place where a pallet can be stored.
After a while, we’ll have one more entity that is capable of storing pallets - a truck.
Thus, let’s create an interface to generalize a pallet container.
package com.company.warehouse.simulation;
import com.company.warehouse.simulation.graph.Node;
/**
* A container that can store pallets. Pallets could be put or taken to/from a container.
* Is located in a graph node.
*/
public interface PalletContainer {
/**
* @param loading do we check loading (<code>true</code>) or unloading (<code>false</code>) availability
* @return if it is possible to load (or unload) one pallet to (or from) the container.
*/
boolean isAvailableFor(boolean loading);
/**
* Loads (or unloads) one pallet to (or from) the container.
* @param loading perform loading (<code>true</code>) or unloading
*/
void placePallet(boolean loading);
Node getNode();
}
PalletPosition
resides in a graph node.
package com.company.warehouse.simulation;
import com.company.warehouse.simulation.graph.Node;
public class PalletPosition implements PalletContainer {
private final Node node;
/**
* Whether a pallet is present here.
*/
private boolean busy;
public PalletPosition(Node node) {
this.node = node;
}
@Override
public Node getNode() {
return node;
}
public boolean isBusy() {
return busy;
}
@Override
public boolean isAvailableFor(boolean loading) {
return busy != loading;
}
@Override
public void placePallet(boolean loading) {
if (busy == loading) {
throw new RuntimeException("Can't " + (busy ? "put to a busy" : "take from a free") + " position");
}
busy = loading;
}
@Override
public String toString() {
return "PalletPosition [" + node + ", busy=" + busy + "]";
}
}
Like we did for the forklifts, we will design a shape for pallet positions.
package com.company.warehouse.application.animation;
import java.awt.Color;
import com.amalgamasimulation.animation.shapes.shapes2d.GroupShape;
import com.amalgamasimulation.animation.shapes.shapes2d.RectangleShape;
import com.amalgamasimulation.geometry.Point;
import com.company.warehouse.simulation.PalletPosition;
public class PalletPositionShape extends GroupShape {
public PalletPositionShape(PalletPosition p) {
super(p.getNode().getPoint());
withShape(new RectangleShape(() -> new Point(-5, -5), () -> 10.0, () -> 10.0)
.withLineColor(Color.gray)
.withFillColor(() -> p.isAvailableFor(false) ? Color.green : Color.gray)
);
}
}
Use the new shapes in the SimulationPart.onShowModel()
method before the forklifts:
private void onShowModel(Model model) {
...
model.getArcs().forEach(r -> animationView.addShape(new ArcShape(r)));
model.getAllPositions().forEach(p -> animationView.addShape(new PalletPositionShape(p)));
model.getForklifts().forEach(f -> animationView.addShape(new ForkliftShape(f)));
}
animationView.adjustWindow();
}
Let’s set our pallet positions in the model.
private List<PalletPosition> allPositions = new ArrayList<>();
public Iterable<PalletPosition> getAllPositions() {
return allPositions;
}
Here we added a container property for storing all the positions.
/**
* @param probability requested probability of the <code>true</code> value.
* @return pseudo-random boolean
*/
public boolean randomTrue(double probability){
return random.nextDouble() < probability;
}
private PalletPosition newPalletPosition(com.company.warehouse.datamodel.Node scenarioNode, boolean busy) {
var node = mapping.nodesMap.get(scenarioNode);
var p = new PalletPosition(node);
if (busy) {
p.placePallet(true);
}
allPositions.add(p);
return p;
}
private void initializeMainStorage() {
scenario.getStoragePlaces().stream()
.forEach(scenarioNode -> newPalletPosition(scenarioNode, randomTrue(0.5)));
}
private void initializeGates() {
for (var scenarioGate : scenario.getGates()) {
scenarioGate.getPlaces().stream()
.forEach(scenarioNode -> newPalletPosition(scenarioNode, randomTrue(0.5)));
}
}
Randomly busy pallet positions are created in the main storage area and at every gate.
Invoke these methods from the constructor.
public Model(Engine engine, Scenario scenario) {
...
graphEnvironment = new GraphEnvironment<>();
// engine.scheduleRelative(0, () -> getAgents().forEach(a -> dispatchAgent(a)));
initializeNodes();
initializeArcs();
initializeMainStorage();
initializeGates();
initializeForklifts();
makeAssignments();
}
Let’s launch the simulation and see many cute green boxes scattered chaotically around the warehouse.
But what is this … ? The forklifts are passing through the boxes:
In this screenshot, the forklift marked with the red circle is moving upwards, so it has just passed through a node occupied by a (green) pallet.
The forklift right below the marked one is OK - it has arrived at the node with a pallet but has not passed through it. |
This behavior does not look realistic. What can we do about it? …