Commit bdba3299 authored by Jakob Schöttl's avatar Jakob Schöttl
Browse files

Merge branch 'seating' into develop

parents 165fbca9 9dcb4ca0
...@@ -8,7 +8,7 @@ import javax.swing.JFileChooser; ...@@ -8,7 +8,7 @@ import javax.swing.JFileChooser;
import org.vadere.gui.components.utils.Resources; import org.vadere.gui.components.utils.Resources;
import org.vadere.gui.topographycreator.model.IDrawPanelModel; import org.vadere.gui.topographycreator.model.IDrawPanelModel;
import org.vadere.gui.topographycreator.utils.JSONWriter; import org.vadere.gui.topographycreator.utils.TopographyJsonWriter;
/** /**
* Action: save the topography to the current file (last_save_point). * Action: save the topography to the current file (last_save_point).
...@@ -43,11 +43,11 @@ public class ActionQuickSaveTopography extends TopographyAction { ...@@ -43,11 +43,11 @@ public class ActionQuickSaveTopography extends TopographyAction {
if (returnVal == JFileChooser.APPROVE_OPTION) { if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile().toString().endsWith(".json") ? fc.getSelectedFile() File file = fc.getSelectedFile().toString().endsWith(".json") ? fc.getSelectedFile()
: new File(fc.getSelectedFile().toString() + ".json"); : new File(fc.getSelectedFile().toString() + ".json");
JSONWriter.writeTopography(getScenarioPanelModel().build(), file); TopographyJsonWriter.writeTopography(getScenarioPanelModel().build(), file);
resources.putProperty("last_save_point", file.getAbsolutePath()); resources.putProperty("last_save_point", file.getAbsolutePath());
} }
} else { } else {
JSONWriter.writeTopography(getScenarioPanelModel().build(), new File(lastSavePoint)); TopographyJsonWriter.writeTopography(getScenarioPanelModel().build(), new File(lastSavePoint));
} }
} }
......
...@@ -8,7 +8,7 @@ import javax.swing.JFileChooser; ...@@ -8,7 +8,7 @@ import javax.swing.JFileChooser;
import org.vadere.gui.components.utils.Resources; import org.vadere.gui.components.utils.Resources;
import org.vadere.gui.topographycreator.model.IDrawPanelModel; import org.vadere.gui.topographycreator.model.IDrawPanelModel;
import org.vadere.gui.topographycreator.utils.JSONWriter; import org.vadere.gui.topographycreator.utils.TopographyJsonWriter;
/** /**
* Action: save the topography to a new file. * Action: save the topography to a new file.
...@@ -40,7 +40,7 @@ public class ActionSaveTopography extends TopographyAction { ...@@ -40,7 +40,7 @@ public class ActionSaveTopography extends TopographyAction {
if (returnVal == JFileChooser.APPROVE_OPTION) { if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile(); File file = fc.getSelectedFile();
// new XMLWriter(getScenarioPanelModel(), file).writeScenario(); // new XMLWriter(getScenarioPanelModel(), file).writeScenario();
JSONWriter.writeTopography(getScenarioPanelModel().build(), file); TopographyJsonWriter.writeTopography(getScenarioPanelModel().build(), file);
resources.setProperty("last_save_point", file.getAbsolutePath()); resources.setProperty("last_save_point", file.getAbsolutePath());
} }
} }
......
...@@ -10,16 +10,12 @@ import org.vadere.state.scenario.Topography; ...@@ -10,16 +10,12 @@ import org.vadere.state.scenario.Topography;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
/** /**
* IO-Class to write a Topography to a certain stream. * Write a topography to a stream.
* *
*/ */
public class JSONWriter { public class TopographyJsonWriter {
/**
* Write the Topography to a certain File. /** Write the topography to a certain file. */
*
* @param topography the topography
* @param file the certain file
*/
public static void writeTopography(final Topography topography, final File file) { public static void writeTopography(final Topography topography, final File file) {
try { try {
writeTopography(topography, new PrintStream(file)); writeTopography(topography, new PrintStream(file));
......
...@@ -193,13 +193,12 @@ public class Simulation { ...@@ -193,13 +193,12 @@ public class Simulation {
logger.info("Simulation interrupted."); logger.info("Simulation interrupted.");
} }
} }
} } finally {
finally {
// this is necessary to free the resources (files), the SimulationWriter and processor are writing in! // this is necessary to free the resources (files), the SimulationWriter and processor are writing in!
postLoop(); postLoop();
processorManager.writeOutput(); processorManager.writeOutput();
logger.info("Logged all processor in logfiles"); logger.info("Finished writing all output files");
} }
} }
......
...@@ -118,14 +118,8 @@ public class SourceController { ...@@ -118,14 +118,8 @@ public class SourceController {
return isAfterSourceEndTime(simTimeInSec) && dynamicElementsToCreate == 0; return isAfterSourceEndTime(simTimeInSec) && dynamicElementsToCreate == 0;
} }
private boolean isMaximumNumberOfSpawnedElementsReached() { private boolean hasNextEvent() {
final int maxNumber = sourceAttributes.getMaxSpawnNumberTotal(); return timeOfNextEvent != null;
return maxNumber != AttributesSource.NO_MAX_SPAWN_NUMBER_TOTAL
&& dynamicElementsCreatedTotal >= maxNumber;
}
private boolean isSourceWithOneSingleSpawnEvent() {
return sourceAttributes.getStartTime() == sourceAttributes.getEndTime();
} }
private void processNextEventWhenItIsTime(double simTimeInSec) { private void processNextEventWhenItIsTime(double simTimeInSec) {
...@@ -154,8 +148,14 @@ public class SourceController { ...@@ -154,8 +148,14 @@ public class SourceController {
processNextEventWhenItIsTime(simTimeInSec); processNextEventWhenItIsTime(simTimeInSec);
} }
private boolean hasNextEvent() { private boolean isMaximumNumberOfSpawnedElementsReached() {
return timeOfNextEvent != null; final int maxNumber = sourceAttributes.getMaxSpawnNumberTotal();
return maxNumber != AttributesSource.NO_MAX_SPAWN_NUMBER_TOTAL
&& dynamicElementsCreatedTotal >= maxNumber;
}
private boolean isSourceWithOneSingleSpawnEvent() {
return sourceAttributes.getStartTime() == sourceAttributes.getEndTime();
} }
private boolean isAfterSourceEndTime(double time) { private boolean isAfterSourceEndTime(double time) {
......
package org.vadere.simulator.models.seating;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import org.apache.commons.math3.distribution.EnumeratedDistribution;
import org.apache.commons.math3.distribution.RealDistribution;
import org.apache.commons.math3.random.JDKRandomGenerator;
import org.apache.commons.math3.random.RandomGenerator;
import org.apache.commons.math3.util.Pair;
import org.apache.log4j.Logger;
import org.vadere.simulator.control.ActiveCallback;
import org.vadere.simulator.models.Model;
import org.vadere.simulator.models.seating.trainmodel.Compartment;
import org.vadere.simulator.models.seating.trainmodel.Seat;
import org.vadere.simulator.models.seating.trainmodel.SeatGroup;
import org.vadere.simulator.models.seating.trainmodel.TrainModel;
import org.vadere.state.attributes.Attributes;
import org.vadere.state.attributes.models.AttributesSeating;
import org.vadere.state.attributes.models.seating.SeatRelativePosition;
import org.vadere.state.attributes.models.seating.model.SeatPosition;
import org.vadere.state.attributes.scenario.AttributesAgent;
import org.vadere.state.scenario.Agent;
import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.scenario.Target;
import org.vadere.state.scenario.TargetListener;
import org.vadere.state.scenario.Topography;
import org.vadere.state.scenario.TrainGeometry;
import org.vadere.util.geometry.shapes.VShape;
import org.vadere.util.math.TruncatedNormalDistribution;
import org.vadere.util.reflection.DynamicClassInstantiator;
/**
* This model can only be used with train scenarios complying with scenarios generated by Traingen.
*
* To enable this model, add this model's class name to the main model's submodel list and
* load a train topography.
*
*/
public class SeatingModel implements ActiveCallback, Model {
private final Logger log = Logger.getLogger(SeatingModel.class);
private AttributesSeating attributes;
private TrainModel trainModel;
private Random random;
/** Used for distributions from Apache Commons Math. */
private RandomGenerator rng;
@Override
public void initialize(List<Attributes> attributesList, Topography topography,
AttributesAgent attributesPedestrian, Random random) {
this.attributes = Model.findAttributes(attributesList, AttributesSeating.class);
DynamicClassInstantiator<TrainGeometry> instantiator = new DynamicClassInstantiator<>();
TrainGeometry trainGeometry = instantiator.createObject(attributes.getTrainGeometry());
try {
trainModel = new TrainModel(topography, trainGeometry);
} catch (Exception e) {
throw new IllegalStateException(String.format("Topography is corrupt or not a %s train.",
trainGeometry.getClass().getSimpleName()), e);
}
this.random = random;
this.rng = new JDKRandomGenerator(random.nextInt());
for (final Target target : trainModel.getCompartmentTargets()) {
target.addListener(compartmentTargetListener);
}
for (final Seat seat : trainModel.getSeats()) {
seat.getAssociatedTarget().addListener(seatTargetListener);
}
}
@Override
public void preLoop(double simTimeInSec) {
// before simulation
}
@Override
public void postLoop(double simTimeInSec) {
// after simulation
}
@Override
public void update(double simTimeInSec) {
// choose compartment for those peds without a target
trainModel.getPedestrians().stream()
.filter(this::hasNoTargetAssigned)
.forEach(this::assignCompartmentTarget);
// the next steps are done by target listeners registered in initialize()
}
private void assignCompartmentTarget(Pedestrian p) {
final int entranceAreaIndex = trainModel.getEntranceAreaIndexForPerson(p);
final Compartment compartment = chooseCompartment(p, entranceAreaIndex);
logAssigningCompartment(p, compartment);
p.addTarget(compartment.getCompartmentTarget());
}
private void logAssigningCompartment(Pedestrian p, final Compartment compartment) {
logDebug("Assigning compartment %d to pedestrian %d", compartment.getIndex(), p.getId());
}
private void assignSeatTarget(Pedestrian p) {
final Compartment compartment = trainModel.getCompartment(p);
if (compartment.isFull()) {
logDebug("Compartment %d is full. No seat available for pedestrian %d. Proceeding to next compartment.",
compartment.getIndex(), p.getId());
proceedToNextCompartmentIfPossible(p);
return;
}
final SeatGroup seatGroup = chooseSeatGroup(compartment);
final Seat seat = chooseSeat(seatGroup);
logDebug("Assigning seat %d.%d to pedestrian %d", compartment.getIndex(),
seat.getSeatNumberWithinCompartment(), p.getId());
p.addTarget(seat.getAssociatedTarget());
}
private void proceedToNextCompartmentIfPossible(Pedestrian p) {
final int fromEntranceAreaIndex = trainModel.getEntranceAreaIndexForPerson(p);
final int compartmentIndex = trainModel.getCompartment(p).getIndex();
if (isInnerCompartment(compartmentIndex)) {
final int direction = getDirectionFromEntranceAreaToCompartment(fromEntranceAreaIndex, compartmentIndex);
final Compartment nextCompartment = trainModel.getCompartment(compartmentIndex + direction);
logAssigningCompartment(p, nextCompartment);
p.addTarget(nextCompartment.getCompartmentTarget());
}
}
boolean isInnerCompartment(final int compartmentIndex) {
return compartmentIndex > 0 && compartmentIndex < trainModel.getCompartmentCount() - 1;
}
int getDirectionFromEntranceAreaToCompartment(int entranceAreaIndex, int compartmentIndex) {
// entrance areas: 0 1 2 3
// compartments: 0 1 2 3 4
if (compartmentIndex <= entranceAreaIndex)
return -1;
else
return +1;
}
private boolean hasNoTargetAssigned(Pedestrian p) {
return p.getTargets().isEmpty();
}
public TrainModel getTrainModel() {
return trainModel;
}
public Compartment chooseCompartment(Pedestrian person, int entranceAreaIndex) {
// entrance areas: 0 1 2 3
// compartments: 0 1 2 3 4
// left- and rightmost compartments are "half-compartments"
final int entranceAreaCount = trainModel.getEntranceAreaCount();
final double distributionMean = entranceAreaIndex + 0.5;
final double distributionSd = entranceAreaCount / 2.0;
final RealDistribution distribution = new TruncatedNormalDistribution(rng, distributionMean, distributionSd,
0, entranceAreaCount, 100);
final double value = distribution.sample();
final int compartmentIndex = (int) Math.round(value);
return trainModel.getCompartment(compartmentIndex);
}
public SeatGroup chooseSeatGroup(Compartment compartment) {
final List<Pair<Boolean, Double>> valuesAndProbabilities = attributes.getSeatGroupChoice();
final EnumeratedDistribution<Boolean> distribution = new EnumeratedDistribution<>(rng, valuesAndProbabilities);
List<SeatGroup> seatGroups = compartment.getSeatGroups().stream()
.filter(sg -> sg.getPersonCount() < 4)
.collect(Collectors.toList());
if (seatGroups.isEmpty()) {
throw new IllegalStateException("No seats available in given compartment.");
}
while (seatGroups.size() > 1) {
final int minPersonCount = getSeatGroupMinPersonCount(seatGroups);
if (allSeatGroupPersonCountsEquals(seatGroups)) {
return drawRandomElement(seatGroups);
}
if (distribution.sample()) {
// choice for seat group with minimal number of other passengers
final List<SeatGroup> minSeatGroups = seatGroups.stream()
.filter(sg -> sg.getPersonCount() == minPersonCount)
.collect(Collectors.toList());
return drawRandomElement(minSeatGroups);
} else {
seatGroups = seatGroups.stream()
.filter(sg -> sg.getPersonCount() != minPersonCount)
.collect(Collectors.toList());
}
}
return seatGroups.get(0);
}
private boolean allSeatGroupPersonCountsEquals(List<SeatGroup> result) {
return result.stream().mapToInt(SeatGroup::getPersonCount).distinct().count() == 1;
}
private int getSeatGroupMinPersonCount(List<SeatGroup> result) {
return result.stream()
.mapToInt(SeatGroup::getPersonCount)
.min().getAsInt();
}
private <T> T drawRandomElement(List<T> list) {
return list.get(random.nextInt(list.size()));
}
public Seat chooseSeat(SeatGroup seatGroup) {
final int personsSitting = seatGroup.getPersonCount();
switch (personsSitting) {
case 0:
return chooseSeat0(seatGroup);
case 1:
return chooseSeat1(seatGroup);
case 2:
return chooseSeat2(seatGroup);
case 3:
return chooseSeat3(seatGroup);
default:
assert personsSitting == 4;
throw new IllegalStateException("Seat group is already full. This method should not have been called!");
}
}
private Seat chooseSeat0(SeatGroup seatGroup) {
final EnumeratedDistribution<SeatPosition> distribution =
new EnumeratedDistribution<>(rng, attributes.getSeatChoice0());
return seatGroup.getSeatByPosition(distribution.sample());
}
private Seat chooseSeat1(final SeatGroup seatGroup) {
final EnumeratedDistribution<SeatRelativePosition> distribution =
new EnumeratedDistribution<>(rng, attributes.getSeatChoice1());
final SeatRelativePosition relativePosition = distribution.sample();
return seatGroup.seatRelativeTo(seatGroup.getTheOccupiedSeat(), relativePosition);
}
private Seat chooseSeat2(final SeatGroup seatGroup) {
return seatGroup.getTheTwoAvailableSeats().get(random.nextInt(2));
}
private Seat chooseSeat3(final SeatGroup seatGroup) {
return seatGroup.getTheAvailableSeat();
}
private void sitDownIfPossible(Pedestrian pedestrian, Seat seat) {
if (seat.getSittingPerson() == null) {
seat.setSittingPerson(pedestrian);
final VShape seatGeometry = seat.getAssociatedTarget().getShape();
pedestrian.setPosition(seatGeometry.getCentroid());
} else {
lookForAlternativeSeat(pedestrian, seat);
}
}
private void lookForAlternativeSeat(Pedestrian pedestrian, Seat seat) {
final SeatGroup seatGroup = seat.getSeatGroup();
if (!seatGroup.isFull()) {
// Try other available seat in same seat group
List<Seat> availableOtherSeats = seatGroup.getSeats().stream()
.filter(s -> s != seat)
.filter(Seat::isAvailable)
.collect(Collectors.toList());
final Seat otherSeat = drawRandomElement(availableOtherSeats);
pedestrian.addTarget(otherSeat.getAssociatedTarget());
} else {
// Go back to compartment target to re-trigger seat assignment
final Compartment compartment = seatGroup.getCompartment();
pedestrian.addTarget(compartment.getCompartmentTarget());
}
}
private void logDebug(String formatString, Object... args) {
log.debug(String.format(formatString, args));
}
private final TargetListener compartmentTargetListener = new TargetListener() {
@Override
public void reachedTarget(Target target, Agent agent) {
assignSeatTarget((Pedestrian) agent);
}
};
private final TargetListener seatTargetListener = new TargetListener() {
@Override
public void reachedTarget(Target target, Agent agent) {
final Seat seat = trainModel.getSeatForTarget(target);
logDebug("Pedestrian %d reached seat %d.%d (%d)", agent.getId(),
seat.getSeatGroup().getCompartment().getIndex(), seat.getSeatNumberWithinCompartment(),
target.getId());
sitDownIfPossible((Pedestrian) agent, trainModel.getSeatForTarget(target));
}
};
}
package org.vadere.simulator.models.seating.dataprocessing;
public class LogEventEntry {
private static final String NA_STRING = "NA";
private final String eventType;
private final Integer extraInt;
private final String extraString;
private final Integer personId;
private final Integer seatNumber;
private final int surveyId;
private final String time;
public LogEventEntry(String time, String eventType, Integer personId, Integer seatNumber, int surveyId) {
this(eventType, null, null, personId, seatNumber, surveyId, time);
}
public LogEventEntry(String eventType, Integer extraInt, String extraString, Integer personId, Integer seatNumber,
int surveyId, String time) {
this.eventType = eventType;
this.extraInt = extraInt;
this.extraString = extraString;
this.personId = personId;
this.seatNumber = seatNumber;
this.surveyId = surveyId;
this.time = time;
}
public String[] toStrings() {
String[] arr = { eventType, stringOrNA(extraInt), stringOrNA(extraString), stringOrNA(personId),
stringOrNA(seatNumber), stringOrNA(surveyId), stringOrNA(time) };
return arr;
}
public static String[] getHeaders() {
// $ head -n1 seating-data/data/LOG_EVENT.csv
// (without "ID" column because it comes from IdDataKey)
String[] headers = { "EVENT_TYPE", "EXTRA_INT", "EXTRA_STRING", "PERSON", "SEAT", "SURVEY", "TIME" };
return headers;
}
@Override
public String toString() {
return String.join(" ", toStrings());
}
private String stringOrNA(Object o) {
return o == null ? NA_STRING : o.toString();
}
}
package org.vadere.simulator.models.seating.dataprocessing;
import org.vadere.simulator.projects.dataprocessing.datakey.IdDataKey;
import org.vadere.simulator.projects.dataprocessing.outputfile.OutputFile;
public class LogEventOutputFile extends OutputFile<IdDataKey> {
public LogEventOutputFile() {
super("ID");
}
@Override
public String[] toStrings(final IdDataKey key) {
return new String[] { Integer.toString(key.getId()) };
}
}
package org.vadere.simulator.models.seating.dataprocessing;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.Set;
import org.vadere.simulator.control.SimulationState;
import org.vadere.simulator.models.MainModel;
import org.vadere.simulator.models.seating.SeatingModel;
import org.vadere.simulator.models.seating.trainmodel.Compartment;
import org.vadere.simulator.models.seating.trainmodel.Seat;
import org.vadere.simulator.models.seating.trainmodel.SeatGroup;
import org.vadere.simulator.models.seating.trainmodel.TrainModel;
import org.vadere.simulator.projects.dataprocessing.ProcessorManager;
import org.vadere.simulator.projects.dataprocessing.datakey.IdDataKey;
import org.vadere.simulator.projects.dataprocessing.processor.DataProcessor;
import org.vadere.state.attributes.exceptions.AttributesNotFoundException;
import org.vadere.state.attributes.processor.AttributesLogEventProcessor;
import org.vadere.state.scenario.Agent;
import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.scenario.Target;
import org.vadere.state.scenario.TargetListener;
import org.vadere.util.data.FindByClass;
/**