ReplayHandler.java
/*-
* #%L
* MATSim Episim
* %%
* Copyright (C) 2020 matsim-org
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.matsim.episim;
import com.google.inject.Inject;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.events.*;
import org.matsim.api.core.v01.network.Link;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.api.internal.HasPersonId;
import org.matsim.core.events.EventsUtils;
import org.matsim.core.events.handler.BasicEventHandler;
import org.matsim.core.gbl.Gbl;
import org.matsim.core.router.TripStructureUtils;
import org.matsim.facilities.ActivityFacility;
import javax.annotation.Nullable;
import java.time.DayOfWeek;
import java.util.*;
/**
* Handler that replays events from {@link EpisimConfigGroup#getInputEventsFile()} with corrected time and attributes.
*/
public final class ReplayHandler {
private static final Logger log = LogManager.getLogger(ReplayHandler.class);
/**
* Needed in createEpisimFacilityId.
*/
private final EpisimConfigGroup episimConfig;
private final Scenario scenario;
private final Map<DayOfWeek, List<Event>> events = new EnumMap<>(DayOfWeek.class);
/**
* Constructor with optional scenario. Events will be read from given {@link EpisimConfigGroup#getInputEventsFiles()}.
*/
@Inject
public ReplayHandler(EpisimConfigGroup config, @Nullable Scenario scenario) {
this.scenario = scenario;
this.episimConfig = config;
this.events.putAll(readEvents(episimConfig));
if (events.size() != 7) {
EnumSet<DayOfWeek> missing = EnumSet.complementOf(EnumSet.copyOf(events.keySet()));
throw new IllegalStateException("Event definition missing for days: " + missing);
}
}
/**
* Constructor for using pre-defined events. A list of events for all weekdays needs to be present.
* Events also have to ordered by time.
*
* @param events ordered events for all weekdays
*/
public ReplayHandler(Map<DayOfWeek, List<Event>> events) {
this.events.putAll(events);
this.scenario = null;
this.episimConfig = null;
}
/**
* Replays event add modifies attributes based on current iteration.
*/
public void replayEvents(final InfectionEventHandler infectionHandler, DayOfWeek day) {
infectionHandler.handleEvents(day, events.get(day));
}
/**
* All available events.
*/
public Map<DayOfWeek, List<Event>> getEvents() {
return new EnumMap<>(events);
}
/**
* Read events as defined in config.
*/
public Map<DayOfWeek, List<Event>> readEvents(EpisimConfigGroup config) {
EnumMap<DayOfWeek, List<Event>> map = new EnumMap<>(DayOfWeek.class);
for (EpisimConfigGroup.EventFileParams input : config.getInputEventsFiles()) {
List<Event> eventsForDay = new ArrayList<>();
EventsManager manager = EventsUtils.createEventsManager();
manager.addHandler(new EventReader(eventsForDay));
EventsUtils.readEvents(manager, input.getPath());
manager.finishProcessing();
log.info("Read in {} events for {}, with time range {} - {}", eventsForDay.size(), input.getDays(), eventsForDay.get(0).getTime(),
eventsForDay.get(eventsForDay.size() - 1).getTime());
for (DayOfWeek day : input.getDays()) {
if (map.containsKey(day))
throw new IllegalStateException("Events for day " + day + " already defined!");
map.put(day, eventsForDay);
}
}
return map;
}
/**
* Replaces all stored events
*
* @param events new events to store
*/
void setEvents(Map<DayOfWeek, List<Event>> events) {
this.events.clear();
this.events.putAll(events);
}
/**
* Helper class to read events one time.
*/
private final class EventReader implements BasicEventHandler {
private final List<Event> events;
private EventReader(List<Event> events) {
this.events = events;
}
@Override
public void handleEvent(Event event) {
// Add coordinate information if not present
if (event instanceof ActivityStartEvent) {
ActivityStartEvent e = (ActivityStartEvent) event;
if (!shouldHandleActivityEvent(e, e.getActType())) {
return;
}
Coord coord = e.getCoord();
if (coord == null && scenario != null && scenario.getNetwork().getLinks().containsKey(e.getLinkId())) {
Link link = scenario.getNetwork().getLinks().get(e.getLinkId());
coord = link.getToNode().getCoord();
}
event = new ActivityStartEvent(e.getTime(), e.getPersonId(), e.getLinkId(),
createEpisimFacilityId(e),
e.getActType().intern(), coord);
} else if (event instanceof ActivityEndEvent) {
ActivityEndEvent e = (ActivityEndEvent) event;
if (!shouldHandleActivityEvent(e, e.getActType())) {
return;
}
String actType = e.getActType().intern();
double time = e.getTime();
event = new ActivityEndEvent(time, e.getPersonId(), e.getLinkId(),
createEpisimFacilityId(e),
actType);
} else if (event instanceof PersonEntersVehicleEvent) {
if (!shouldHandlePersonEvent((PersonEntersVehicleEvent) event)) {
return;
}
} else if (event instanceof PersonLeavesVehicleEvent) {
if (!shouldHandlePersonEvent((PersonLeavesVehicleEvent) event)) {
return;
}
}
events.add(event);
}
}
/**
* Whether {@code event} should be handled.
*
* @param actType activity type
*/
public static boolean shouldHandleActivityEvent(HasPersonId event, String actType) {
// ignore drt and stage activities
return !event.getPersonId().toString().startsWith("drt") && !event.getPersonId().toString().startsWith("rt")
&& !TripStructureUtils.isStageActivityType(actType);
}
/**
* Whether a Person event (e.g. {@link PersonEntersVehicleEvent} should be handled.
*/
public static boolean shouldHandlePersonEvent(HasPersonId event) {
// ignore pt drivers and drt
String id = event.getPersonId().toString();
return !id.startsWith("pt_pt") && !id.startsWith("pt_tr") && !id.startsWith("drt") && !id.startsWith("rt");
}
private Id<ActivityFacility> createEpisimFacilityId(HasFacilityId event) {
if (episimConfig.getFacilitiesHandling() == EpisimConfigGroup.FacilitiesHandling.snz) {
Id<ActivityFacility> id = event.getFacilityId();
if (id == null)
throw new IllegalStateException("No facility id present. Please switch to episimConfig.setFacilitiesHandling( EpisimConfigGroup.FacilitiesHandling.bln ) ");
return id;
} else if (episimConfig.getFacilitiesHandling() == EpisimConfigGroup.FacilitiesHandling.bln) {
if (event instanceof ActivityStartEvent) {
ActivityStartEvent theEvent = (ActivityStartEvent) event;
return Id.create(theEvent.getActType().split("_")[0] + "_" + theEvent.getLinkId().toString(), ActivityFacility.class);
} else if (event instanceof ActivityEndEvent) {
ActivityEndEvent theEvent = (ActivityEndEvent) event;
return Id.create(theEvent.getActType().split("_")[0] + "_" + theEvent.getLinkId().toString(), ActivityFacility.class);
} else {
throw new IllegalStateException("unexpected event type=" + ((Event) event).getEventType());
}
} else {
throw new NotImplementedException(Gbl.NOT_IMPLEMENTED);
}
}
}