Commit 4af09baa authored by Benedikt Zoennchen's avatar Benedikt Zoennchen
Browse files

add new processors which compute the fundamental diagram and add a VTrajectory...

add new processors which compute the fundamental diagram and add a VTrajectory class which supports some helper method to analyse a trajectory.
parent 4dbdad25
package org.vadere.simulator.projects.dataprocessing.processor;
import org.jetbrains.annotations.NotNull;
import org.vadere.simulator.control.SimulationState;
import org.vadere.state.scenario.Agent;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VRectangle;
import org.vadere.util.voronoi.Face;
import org.vadere.util.voronoi.VoronoiDiagram;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* @author Mario Teixeira Parente
* Given a Simulation state this Algorithm computes the Voronoi density defined in zoennchen-2013 section 3, equation 3.7.
*
* @author Benedikt Zoennchen
*
*/
public class AreaDensityVoronoiAlgorithm extends AreaDensityAlgorithm {
private VRectangle measurementArea;
private VRectangle voronoiArea;
......@@ -28,36 +30,31 @@ public class AreaDensityVoronoiAlgorithm extends AreaDensityAlgorithm {
@Override
public double getDensity(final SimulationState state) {
VoronoiDiagram voronoiDiagram = new VoronoiDiagram(this.voronoiArea);
// convert pedestrians to positions
List<VPoint> pedestrianPositions = Agent.getPositions(state.getTopography().getElements(Agent.class));
voronoiDiagram.computeVoronoiDiagram(pedestrianPositions);
// compute everything
List<org.vadere.util.voronoi.Face> faces = voronoiDiagram.getFaces();
List<Face> faces = generateFaces(state);
Map<Integer, Double> areaMap = new TreeMap<>();
Map<Integer, VPoint> faceMap = new TreeMap<>();
double area = 0.0;
int pedCount = 0;
if (faces != null) {
for (org.vadere.util.voronoi.Face face : faces) {
areaMap.put(face.getId(), face.computeArea());
faceMap.put(face.getId(), face.getSite());
for (Face face : faces) {
if (this.measurementArea.contains(face.getSite())) {
area += face.computeArea();
pedCount++;
}
}
return pedCount > 0 ? pedCount / area : 0;
}
double area = 0.0;
int pedCount = 0;
private List<Face> generateFaces(@NotNull final SimulationState state) {
VoronoiDiagram voronoiDiagram = new VoronoiDiagram(this.voronoiArea);
// TODO: Possible optimization (do not test all faces)
for (Integer site : faceMap.keySet()) {
if (this.measurementArea.contains(faceMap.get(site))) {
area += areaMap.get(site);
pedCount++;
}
}
// convert pedestrians to positions
List<VPoint> pedestrianPositions = Agent.getPositions(state.getTopography().getElements(Agent.class));
voronoiDiagram.computeVoronoiDiagram(pedestrianPositions);
return pedCount > 0 ? pedCount / area : 0;
}
// compute everything
List<Face> faces = voronoiDiagram.getFaces();
return faces == null ? Collections.emptyList() : faces;
}
}
......@@ -101,7 +101,7 @@ public abstract class DataProcessor<K extends DataKey<K>, V> {
this.data.put(key, value);
}
public void preLoop(final SimulationState state) { }
public void preLoop(final SimulationState state) { data.clear(); }
protected abstract void doUpdate(final SimulationState state);
......
package org.vadere.simulator.projects.dataprocessing.processor;
import org.vadere.simulator.control.SimulationState;
/**
* @author Mario Teixeira Parente
*
*/
public interface IAreaVelocityAlgorithm {
String getName();
double getVelocity(final SimulationState state);
}
package org.vadere.simulator.projects.dataprocessing.processor;
import org.jetbrains.annotations.NotNull;
import org.vadere.simulator.control.SimulationState;
import org.vadere.state.scenario.Agent;
import org.vadere.util.geometry.GeometryUtils;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VPolygon;
import org.vadere.util.geometry.shapes.VRectangle;
import org.vadere.util.voronoi.Face;
import org.vadere.util.voronoi.HalfEdge;
import org.vadere.util.voronoi.VoronoiDiagram;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Given a Simulation state this Algorithm computes the Voronoi density and Voronoi velocity
* defined in zhang-2011 (doi:10.1088/1742-5468/2011/06/P06004) section 3.4 (Method D), equations (7, 8, 9)
*
* @author Benedikt Zoennchen
*
*/
public class IntegralVoronoiAlgorithm extends AreaDensityAlgorithm implements IAreaVelocityAlgorithm {
private VRectangle measurementArea;
private VRectangle voronoiArea;
public IntegralVoronoiAlgorithm(@NotNull final VRectangle measurementArea, @NotNull final VRectangle voronoiArea) {
super("areaVoronoi");
this.measurementArea = measurementArea;
this.voronoiArea = voronoiArea;
}
@Override
public double getDensity(final SimulationState state) {
List<Face> faces = generateFaces(state);
double area = 0.0;
for (Face face : faces) {
if (intersectMeasurementArea(face)) {
area += face.computeArea();
}
}
return area / measurementArea.getArea();
}
private List<Face> generateFaces(@NotNull final SimulationState state) {
VoronoiDiagram voronoiDiagram = new VoronoiDiagram(this.voronoiArea);
// convert pedestrians to positions
List<VPoint> pedestrianPositions = Agent.getPositions(state.getTopography().getElements(Agent.class));
voronoiDiagram.computeVoronoiDiagram(pedestrianPositions);
// compute everything
List<Face> faces = voronoiDiagram.getFaces();
return faces == null ? Collections.emptyList() : faces;
}
@Override
public double getVelocity(SimulationState state) {
VoronoiDiagram voronoiDiagram = new VoronoiDiagram(this.voronoiArea);
// convert pedestrians to positions
List<VPoint> pedestrianPositions = Agent.getPositions(state.getTopography().getElements(Agent.class));
voronoiDiagram.computeVoronoiDiagram(pedestrianPositions);
// compute everything
List<Face> faces = voronoiDiagram.getFaces();
Map<Integer, Double> areaMap = new TreeMap<>();
Map<Integer, Face> faceMap = new TreeMap<>();
double velocity = 0.0;
for (Face face : faces) {
areaMap.put(face.getId(), face.computeArea());
faceMap.put(face.getId(), face);
if (intersectMeasurementArea(face)) {
VPoint center = face.getSite();
Agent ped = state.getTopography().getSpatialMap(Agent.class).getObjects(center, 0.2)
.stream()
.filter(agent -> center.distance(agent.getPosition()) < 0.01)
.findAny().get();
velocity += (face.computeArea() * ped.getVelocity().getLength());
}
}
return velocity / measurementArea.getArea();
}
private boolean intersectMeasurementArea(@NotNull final Face face) {
return measurementArea.intersects(toPolygon(face));
}
private VPolygon toPolygon(@NotNull final Face face) {
List<VPoint> points = new ArrayList<>();
HalfEdge start = face.getOuterComponent();
HalfEdge next = start;
do {
points.add(next.getOrigin());
next = next.getNext();
} while (start.equals(next));
return GeometryUtils.toPolygon(points);
}
}
package org.vadere.simulator.projects.dataprocessing.processor;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.vadere.annotation.factories.dataprocessors.DataProcessorClass;
import org.vadere.simulator.control.SimulationState;
import org.vadere.simulator.projects.dataprocessing.ProcessorManager;
import org.vadere.simulator.projects.dataprocessing.Utils;
import org.vadere.simulator.projects.dataprocessing.datakey.PedestrianIdKey;
import org.vadere.state.attributes.processor.AttributesCrossingTimeProcessor;
import org.vadere.state.attributes.processor.AttributesProcessor;
import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.simulation.FootStep;
import org.vadere.util.geometry.shapes.VRectangle;
import java.util.Collection;
//TODO
@DataProcessorClass()
public class PedestrianCrossingTimeProcessor extends DataProcessor<PedestrianIdKey, Pair<Double, Double>>{
private VRectangle measurementArea;
private static Logger logger = LogManager.getLogger(PedestrianCrossingTimeProcessor.class);
public PedestrianCrossingTimeProcessor() {
super("crossStartTime", "crossEndTime");
setAttributes(new AttributesCrossingTimeProcessor());
}
@Override
protected void doUpdate(SimulationState state) {
Collection<Pedestrian> peds = state.getTopography().getElements(Pedestrian.class);
for(Pedestrian ped : peds) {
PedestrianIdKey key = new PedestrianIdKey(ped.getId());
for(FootStep footStep : ped.getFootSteps()) {
if(footStep.intersects(measurementArea)) {
double intersectionTime = footStep.computeIntersectionTime(measurementArea);
if(!hasCrossStartTime(key)) {
setStartTime(key, intersectionTime);
}
else if(!hasCrossEndTime(key)) {
setEndTime(key, intersectionTime);
}
else {
assert false : "agent("+key.getPedestrianId()+") crosses the measurement area more than twice!";
logger.error("agent("+key.getPedestrianId()+") crosses the measurement area more than twice!");
}
}
}
}
}
private void setStartTime(@NotNull final PedestrianIdKey key, double time) {
putValue(key, Pair.of(time, Double.POSITIVE_INFINITY));
}
private void setEndTime(@NotNull final PedestrianIdKey key, double time) {
putValue(key, Pair.of(getValue(key).getLeft(), time));
}
private boolean hasCrossStartTime(@NotNull final PedestrianIdKey key) {
Pair<Double, Double> times = getValue(key);
return times == null || times.getLeft().equals(Double.POSITIVE_INFINITY);
}
private boolean hasCrossEndTime(@NotNull final PedestrianIdKey key) {
Pair<Double, Double> times = getValue(key);
return hasCrossStartTime(key) && !times.getRight().equals(Double.POSITIVE_INFINITY);
}
@Override
public void init(final ProcessorManager manager) {
super.init(manager);
AttributesCrossingTimeProcessor att = (AttributesCrossingTimeProcessor) this.getAttributes();
this.measurementArea = att.getMeasurementArea();
}
@Override
public AttributesProcessor getAttributes() {
if (super.getAttributes() == null) {
setAttributes(new AttributesCrossingTimeProcessor());
}
return super.getAttributes();
}
@Override
public String[] toStrings(@NotNull final PedestrianIdKey key) {
Pair<Double, Double> times = getValue(key);
return new String[]{Double.toString(times.getLeft()), Double.toString(times.getRight())};
}
}
......@@ -3,49 +3,45 @@ package org.vadere.simulator.projects.dataprocessing.processor;
import org.vadere.annotation.factories.dataprocessors.DataProcessorClass;
import org.vadere.simulator.control.SimulationState;
import org.vadere.simulator.projects.dataprocessing.ProcessorManager;
import org.vadere.simulator.projects.dataprocessing.datakey.PedestrianIdKey;
import org.vadere.simulator.projects.dataprocessing.datakey.TimestepKey;
import org.vadere.simulator.projects.dataprocessing.datakey.TimestepPedestrianIdKey;
import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.simulation.FootStep;
import org.vadere.state.simulation.VTrajectory;
import org.vadere.state.util.StateJsonConverter;
import java.util.LinkedList;
import java.util.Map;
import java.util.stream.Collectors;
/**
* During one time step a pedestrian my move multiple times which is saved by {@link Pedestrian#getFootSteps()}, i.e. the list of {@link FootStep}s
* will be adjusted after each update(simTimeInSec) call such that it contains the foot steps which started at the lastSimTimeInSec!
* <p>During one time step a pedestrian my move multiple times which is saved by
* {@link Pedestrian#getFootSteps()}, i.e. the {@link VTrajectory} will be adjusted
* after each update(simTimeInSec) call such that it contains the foot steps which
* started at the lastSimTimeInSec!</p>
*
* This processor writes out all those {@link FootStep}s using the standard JSON-format, e.g. one foot steps:
* <p>This processor writes out all those {@link FootStep}s using the standard JSON-format, e.g. one foot steps:
* [{"startTime":26.588661014252686,"endTime":27.123123483931312,"start":{"x":29.4730189272315,"y":24.965262390895376},"end":{"x":29.59817287115996,"y":25.182035380547074}}]
* </p>
*
* <p>This is especially useful if one
* uses the {@link org.vadere.simulator.models.osm.OptimalStepsModel} or any other
* {@link org.vadere.simulator.models.MainModel} for which pedestrians do multiple steps during
* a simulation time step.</p>
*
* @author Benedikt Zoennchen
*/
@DataProcessorClass()
public class PedestrianFootStepProcessor extends DataProcessor<TimestepPedestrianIdKey, LinkedList<FootStep>>{
private double lastSimTime;
public class PedestrianFootStepProcessor extends DataProcessor<TimestepPedestrianIdKey, VTrajectory>{
public PedestrianFootStepProcessor() {
super("strides");
lastSimTime = 0.0;
}
@Override
protected void doUpdate(final SimulationState state) {
Integer timeStep = state.getStep();
for (Pedestrian pedestrian : state.getTopography().getElements(Pedestrian.class)) {
LinkedList<FootStep> copy = pedestrian.getFootSteps()
.stream()
//.filter(footStep -> footStep.getEndTime() > lastSimTime)
//.filter(footStep -> footStep.getEndTime() <= state.getSimTimeInSec())
.collect(Collectors.toCollection(LinkedList::new));
VTrajectory copy = pedestrian.getFootSteps().clone();
putValue(new TimestepPedestrianIdKey(timeStep, pedestrian.getId()), copy);
}
lastSimTime = state.getSimTimeInSec();
}
@Override
......@@ -55,7 +51,7 @@ public class PedestrianFootStepProcessor extends DataProcessor<TimestepPedestria
@Override
public String[] toStrings(TimestepPedestrianIdKey key) {
LinkedList<FootStep> strides = this.getValue(key);
LinkedList<FootStep> strides = this.getValue(key).getFootSteps();
StringBuilder builder = new StringBuilder();
if(strides == null) {
......
package org.vadere.simulator.projects.dataprocessing.processor;
import org.apache.commons.lang3.tuple.Pair;
import org.vadere.annotation.factories.dataprocessors.DataProcessorClass;
import org.vadere.simulator.control.SimulationState;
import org.vadere.simulator.projects.dataprocessing.ProcessorManager;
import org.vadere.simulator.projects.dataprocessing.datakey.PedestrianIdKey;
import org.vadere.simulator.projects.dataprocessing.datakey.TimestepKey;
import org.vadere.simulator.projects.dataprocessing.datakey.TimestepPedestrianIdKey;
import org.vadere.state.attributes.processor.AttributesFlowOverTimeProcessor;
import org.vadere.state.attributes.processor.AttributesFundamentalDiagramAProcessor;
import org.vadere.state.attributes.processor.AttributesProcessor;
import java.util.Arrays;
......@@ -15,26 +14,41 @@ import java.util.List;
import java.util.Map;
/**
* see zhang-2011 Method A.
* <p>This processor computes the fundamental diagram by computing an (average) <tt>flow, density</tt> and
* <tt>velocity</tt> over a certain time (<tt>deltaTime</tt>). This is done by counting the number of pedestrians crossing
* a line for some <tt>deltaTime</tt>. If P is the set of pedestrians crossing in that duration
* (<tt>deltaTime</tt>) the <tt>flow</tt> is defined by: the size of P divided by (te - ts) where
* ts is the time the first pedestrian crossed the line and te is the time the last pedestrian crossed
* the line i.e. (te - ts) is smaller or equals <tt>deltaTime</tt>. The <tt>velocity</tt> of a pedestrian
* crossing the line is its velocity the time-step time in which the crossing happens. So it is not
* exactly the crossing time (plus, minis simTimeStep). The <tt>density</tt> is defined by:
* <tt>flow</tt> divided by (<tt>velocity</tt> times the length of the crossing line). Therefore, the
* crossing line has to be defined appropriately. In addition the processor writes out <tt>deltaTime</tt>,
* and the <tt>measurementTime</tt>. The first <tt>measurementTime</tt> is equal to <tt>deltaTime</tt> divided
* by 2, the second is <tt>deltaTime</tt> plus <tt>deltaTime</tt> divided by 2 and so on.</p>
*
* <p>For more details see zhang-2011 (doi:10.1088/1742-5468/2011/06/P06004) Method A.</p>
*
* @author Benedikt Zoennchen
*
*/
@DataProcessorClass()
public class PedestrianFlowOverTimeProcessor extends DataProcessor<TimestepKey, List<Double>> {
public class PedestrianFundamentalDiagramAProcessor extends DataProcessor<TimestepKey, List<Double>> {
private double deltaTime;
private double deltaSimTime;
private PedestrianLineCrossProcessor pedestrianLineCrossProcessor;
private PedestrianVelocityProcessor pedestrianVelocityProcessor;
public PedestrianFlowOverTimeProcessor() {
super("time", "deltaTime", "flow", "velocity", "density");
public PedestrianFundamentalDiagramAProcessor() {
super("measurementTime", "deltaTime", "flow", "velocity", "density");
}
@Override
public void init(final ProcessorManager manager) {
super.init(manager);
AttributesFlowOverTimeProcessor att = (AttributesFlowOverTimeProcessor) this.getAttributes();
AttributesFundamentalDiagramAProcessor att = (AttributesFundamentalDiagramAProcessor) this.getAttributes();
deltaTime = att.getDeltaTime();
deltaSimTime = -1;
pedestrianLineCrossProcessor = (PedestrianLineCrossProcessor) manager.getProcessor(att.getPedestrianLineCrossProcessorId());
pedestrianVelocityProcessor = (PedestrianVelocityProcessor) manager.getProcessor(att.getPedestrianVelocityProcessorId());
}
......@@ -42,11 +56,17 @@ public class PedestrianFlowOverTimeProcessor extends DataProcessor<TimestepKey,
@Override
public AttributesProcessor getAttributes() {
if (super.getAttributes() == null) {
setAttributes(new AttributesFlowOverTimeProcessor());
setAttributes(new AttributesFundamentalDiagramAProcessor());
}
return super.getAttributes();
}
@Override
public void preLoop(SimulationState state) {
super.preLoop(state);
deltaSimTime = -1;
}
@Override
protected void doUpdate(SimulationState state) {
if(deltaSimTime < 0 && state.getSimTimeInSec() > 0) {
......
package org.vadere.simulator.projects.dataprocessing.processor;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.jetbrains.annotations.NotNull;
import org.vadere.annotation.factories.dataprocessors.DataProcessorClass;
import org.vadere.simulator.control.SimulationState;
import org.vadere.simulator.projects.dataprocessing.ProcessorManager;
import org.vadere.simulator.projects.dataprocessing.datakey.PedestrianIdKey;
import org.vadere.state.attributes.processor.AttributesFundamentalDiagramBProcessor;
import org.vadere.state.attributes.processor.AttributesFundamentalDiagramAProcessor;
import org.vadere.state.attributes.processor.AttributesProcessor;
import org.vadere.state.simulation.VTrajectory;
import org.vadere.util.geometry.shapes.VRectangle;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.stream.Collectors;
/**
* <p>This processor computes the fundamental diagram by computing an (average) velocity and the density
* over a certain area <tt>measurementArea</tt>. The <tt>velocity</tt> of a pedestrian is the distance walked inside
* the <tt>measurementArea</tt> divided by the required time. The <tt>density</tt> of a pedestrian is the integral
* of the number of pedestrians inside the <tt>measurementArea</tt> (integrated over the time) divided by the area
* of the <tt>measurementArea</tt> and the time required to walk through the <tt>measurementArea</tt>. The bound of
* integration is the time the pedestrians enters the <tt>measurementArea</tt> and the time the pedestrian exits
* the <tt>measurementArea</tt>.</p>
*
* <p>For more details see zhang-2011 (doi:10.1088/1742-5468/2011/06/P06004) Method B.</p>
*
* @author Benedikt Zoennchen
*/
@DataProcessorClass()
public class PedestrianFundamentalDiagramBProcessor extends DataProcessor<PedestrianIdKey, Pair<Double, Double>> {
private VRectangle measurementArea;
private PedestrianTrajectoryProcessor pedestrianTrajectoryProcessor;
public PedestrianFundamentalDiagramBProcessor() {
super("velocity", "density");
}
@Override
public void init(final ProcessorManager manager) {
super.init(manager);
AttributesFundamentalDiagramBProcessor att = (AttributesFundamentalDiagramBProcessor) this.getAttributes();
pedestrianTrajectoryProcessor = (PedestrianTrajectoryProcessor) manager.getProcessor(att.getPedestrianTrajectoryProcessorId());
measurementArea = att.getMeasurementArea();
}
@Override
public AttributesProcessor getAttributes() {
if (super.getAttributes() == null) {
setAttributes(new AttributesFundamentalDiagramAProcessor());
}
return super.getAttributes();
}
@Override
public void preLoop(SimulationState state) {
super.preLoop(state);
}
@Override
protected void doUpdate(SimulationState state) {
pedestrianTrajectoryProcessor.doUpdate(state);
}
@Override