Skip to content

Commit

Permalink
Merge pull request #3202 from matsim-org/refactor-parking-cost-handler
Browse files Browse the repository at this point in the history
Refactor parking cost handler
  • Loading branch information
paulheinr committed Apr 10, 2024
2 parents 208f71f + 291eb75 commit 77f1b7a
Show file tree
Hide file tree
Showing 3 changed files with 558 additions and 129 deletions.
Expand Up @@ -36,7 +36,6 @@ public class ParkingCostConfigGroup extends ReflectiveConfigGroup {
public ParkingCostConfigGroup() {
super(GROUP_NAME);
}

private String mode = "car";
private String dailyParkingCostLinkAttributeName = "dailyPCost";
private String firstHourParkingCostLinkAttributeName = "oneHourPCost";
Expand Down
Expand Up @@ -19,12 +19,9 @@

package playground.vsp.simpleParkingCostHandler;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;

import org.apache.commons.lang.StringUtils;
import com.google.common.annotations.VisibleForTesting;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.events.ActivityEndEvent;
Expand All @@ -41,39 +38,39 @@
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.config.groups.QSimConfigGroup;
import org.matsim.core.controler.events.AfterMobsimEvent;
import org.matsim.core.controler.listener.AfterMobsimListener;
import org.matsim.core.router.StageActivityTypeIdentifier;

import com.google.inject.Inject;
import org.matsim.utils.objectattributes.attributable.Attributes;

/**
* @author ikaddoura
*/

/**
* This class handles parking costs for different types of parking activities.
* There are still things to refactor:
* - There are lots of magic numbers.
* - The method handleEvent(PersonEntersVehicleEvent event) is too long.
* - Helpful comments are missing.
* At least, there is a test class for this class. (paul april 2024)
*/
final class ParkingCostHandler implements TransitDriverStartsEventHandler, ActivityEndEventHandler, PersonDepartureEventHandler, PersonLeavesVehicleEventHandler, PersonEntersVehicleEventHandler {

private final Map<Id<Person>, Double> personId2lastLeaveVehicleTime = new HashMap<>();
private final Map<Id<Person>, String> personId2previousActivity = new HashMap<>();
private final Map<Id<Person>, Id<Link>> personId2relevantModeLinkId = new HashMap<>();
private final Map<Id<Person>, Id<Link>> personId2homeLinkId = new HashMap<>();
private final Set<Id<Person>> ptDrivers = new HashSet<>();
private final Set<Id<Person>> hasAlreadyPaidDailyResidentialParkingCosts = new HashSet<>();
private double compensationTime = Double.NaN;


@Inject
private ParkingCostConfigGroup parkingCostConfigGroup;

@Inject
private EventsManager events;

@Inject
private Scenario scenario;

@Inject
private QSimConfigGroup qSimConfigGroup;

private Scenario scenario;

@Override
public void reset(int iteration) {
Expand All @@ -84,143 +81,188 @@ public void reset(int iteration) {
this.ptDrivers.clear();
}


@Override
public void handleEvent(TransitDriverStartsEvent event) {
ptDrivers.add(event.getDriverId());
}


@Override
public void handleEvent(ActivityEndEvent event) {
public void handleEvent(ActivityEndEvent event) {
if (ptDrivers.contains(event.getPersonId())) {
// skip pt drivers
} else {
if (!(StageActivityTypeIdentifier.isStageActivity(event.getActType()))) {

personId2previousActivity.put(event.getPersonId(), event.getActType());

if (personId2relevantModeLinkId.get(event.getPersonId()) != null) {
personId2relevantModeLinkId.remove(event.getPersonId());
}
}
}
return;
}
if ((StageActivityTypeIdentifier.isStageActivity(event.getActType()))) {
return;
}
personId2previousActivity.put(event.getPersonId(), event.getActType());

if(event.getPersonId()!= null){
personId2relevantModeLinkId.remove(event.getPersonId());
}
}


@Override
public void handleEvent(PersonDepartureEvent event) {
if (! ptDrivers.contains(event.getPersonId())) {
// There might be several departures during a single trip.
if (event.getLegMode().equals(parkingCostConfigGroup.getMode())) {
personId2relevantModeLinkId.put(event.getPersonId(), event.getLinkId());
}
if (ptDrivers.contains(event.getPersonId())) {
return;
}

// There might be several departures during a single trip.
if (!event.getLegMode().equals(parkingCostConfigGroup.getMode())) {
return;
}

personId2relevantModeLinkId.put(event.getPersonId(), event.getLinkId());
}

@Override
public void handleEvent(PersonLeavesVehicleEvent event) {
if (ptDrivers.contains(event.getPersonId())) {
return;
}

personId2lastLeaveVehicleTime.put(event.getPersonId(), event.getTime());
}

@Override
public void handleEvent(PersonEntersVehicleEvent event) {
if (! ptDrivers.contains(event.getPersonId())) {
if (personId2relevantModeLinkId.get(event.getPersonId()) != null) {

Link link = scenario.getNetwork().getLinks().get(personId2relevantModeLinkId.get(event.getPersonId()));

if (parkingCostConfigGroup.getActivityPrefixesToBeExcludedFromParkingCost().stream()
.noneMatch(s -> personId2previousActivity.get(event.getPersonId()).startsWith(s))){

if (personId2previousActivity.get(event.getPersonId()).startsWith(parkingCostConfigGroup.getActivityPrefixForDailyParkingCosts())) {
// daily residential parking costs

if (! hasAlreadyPaidDailyResidentialParkingCosts.contains(event.getPersonId())){
hasAlreadyPaidDailyResidentialParkingCosts.add(event.getPersonId());

double residentialParkingFeePerDay = 0.;
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getResidentialParkingFeeAttributeName()) != null) {
residentialParkingFeePerDay = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getResidentialParkingFeeAttributeName());
}

if (residentialParkingFeePerDay > 0.) {
double amount = -1. * residentialParkingFeePerDay;
events.processEvent(new PersonMoneyEvent(event.getTime(), event.getPersonId(), amount, "residential parking", "city", "link " + link.getId().toString()));
}
}

} else {
// other parking cost types

double parkingStartTime = 0.;
if (personId2lastLeaveVehicleTime.get(event.getPersonId()) != null) {
parkingStartTime = personId2lastLeaveVehicleTime.get(event.getPersonId());
}
int parkingDurationHrs = (int) Math.ceil((event.getTime() - parkingStartTime) / 3600.);

double extraHourParkingCosts = 0.;
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getExtraHourParkingCostLinkAttributeName()) != null) {
extraHourParkingCosts = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getExtraHourParkingCostLinkAttributeName());
}

double firstHourParkingCosts = 0.;
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getFirstHourParkingCostLinkAttributeName()) != null) {
firstHourParkingCosts = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getFirstHourParkingCostLinkAttributeName());
}

double dailyParkingCosts = firstHourParkingCosts + 29 * extraHourParkingCosts;
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getDailyParkingCostLinkAttributeName()) != null) {
dailyParkingCosts = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getDailyParkingCostLinkAttributeName());
}

double maxDailyParkingCosts = dailyParkingCosts;
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getMaxDailyParkingCostLinkAttributeName()) != null) {
maxDailyParkingCosts = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getMaxDailyParkingCostLinkAttributeName());
}

double maxParkingDurationHrs = 30;
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getMaxParkingDurationAttributeName()) != null) {
maxParkingDurationHrs = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getMaxParkingDurationAttributeName());
}

double parkingPenalty = 0.;
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getParkingPenaltyAttributeName()) != null) {
parkingPenalty = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getParkingPenaltyAttributeName());
}

double costs = 0.;
if (parkingDurationHrs > 0) {
costs += firstHourParkingCosts;
costs += (parkingDurationHrs - 1) * extraHourParkingCosts;
}
if (costs > dailyParkingCosts) {
costs = dailyParkingCosts;
}
if (costs > maxDailyParkingCosts) {
costs = maxDailyParkingCosts;
}
if ((parkingDurationHrs > maxParkingDurationHrs) & (costs < parkingPenalty)) {
costs = parkingPenalty;
}

if (costs > 0.) {
double amount = -1. * costs;
events.processEvent(new PersonMoneyEvent(event.getTime(), event.getPersonId(), amount, "non-residential parking", "city", "link " + link.getId().toString()));
}

}

}
// Preliminaries
if (ptDrivers.contains(event.getPersonId())) {
return;
}
if (personId2relevantModeLinkId.get(event.getPersonId()) == null) {
return;
}

if (parkingCostConfigGroup.getActivityPrefixesToBeExcludedFromParkingCost().stream()
.anyMatch(s -> personId2previousActivity.get(event.getPersonId()).startsWith(s))) {
return;
}

Link link = scenario.getNetwork().getLinks().get(personId2relevantModeLinkId.get(event.getPersonId()));
Attributes attributes = link.getAttributes();

if (personId2previousActivity.get(event.getPersonId()).startsWith(parkingCostConfigGroup.getActivityPrefixForDailyParkingCosts())) {
// daily residential parking costs
if (hasAlreadyPaidDailyResidentialParkingCosts.contains(event.getPersonId())){
// has already paid daily residential parking costs
return;
}
hasAlreadyPaidDailyResidentialParkingCosts.add(event.getPersonId());

double residentialParkingFeePerDay = getResidentialParkingFeePerDay(attributes);

if (residentialParkingFeePerDay > 0.) {
double amount = -1. * residentialParkingFeePerDay;
events.processEvent(createPersonMoneyEvent(event, amount, link, "residential parking"));
}
return;
}

// other parking cost types

double costs = calculateNonResidentialParkingCosts(event, attributes);

if (costs <= 0.) {
return;
}
double amount = -1. * costs;
events.processEvent(createPersonMoneyEvent(event, amount, link, "non-residential parking"));
}

private PersonMoneyEvent createPersonMoneyEvent(PersonEntersVehicleEvent event, double amount, Link link, String purpose) {
return new PersonMoneyEvent(event.getTime(), event.getPersonId(), amount, purpose, "city", "link " + link.getId());
}

@Override
public void handleEvent(PersonLeavesVehicleEvent event) {
if (! ptDrivers.contains(event.getPersonId())) {
personId2lastLeaveVehicleTime.put(event.getPersonId(), event.getTime());
private double calculateNonResidentialParkingCosts(PersonEntersVehicleEvent event, Attributes attributes) {
double costs = 0.;

int parkingDurationHrs = (int) Math.ceil((event.getTime() - getParkingStartTime(event)) / 3600.);
double firstHourParkingCosts = getFirstHourParkingCosts(attributes);
double extraHourParkingCosts = getExtraHourParkingCosts(attributes);

if (parkingDurationHrs > 0) {
costs += firstHourParkingCosts;
costs += (parkingDurationHrs - 1) * extraHourParkingCosts;
}

double dailyParkingCosts = getDailyParkingCosts(attributes, firstHourParkingCosts, extraHourParkingCosts);

if (costs > dailyParkingCosts) {
costs = dailyParkingCosts;
}

double maxDailyParkingCosts = getMaxDailyParkingCosts(attributes, dailyParkingCosts);

if (costs > maxDailyParkingCosts) {
costs = maxDailyParkingCosts;
}

double maxParkingDurationHrs = getMaxParkingDurationHrs(attributes);
double parkingPenalty = getParkingPenalty(attributes);

if ((parkingDurationHrs > maxParkingDurationHrs) & (costs < parkingPenalty)) {
costs = parkingPenalty;
}
return costs;
}

private double getResidentialParkingFeePerDay(Attributes attributes) {
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getResidentialParkingFeeAttributeName())).orElse(0.);
}

private double getParkingStartTime(PersonEntersVehicleEvent event) {
return Optional.ofNullable(personId2lastLeaveVehicleTime.get(event.getPersonId())).orElse(0.);
}

private double getExtraHourParkingCosts(Attributes attributes) {
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getExtraHourParkingCostLinkAttributeName())).orElse(0.);
}

private double getFirstHourParkingCosts(Attributes attributes) {
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getFirstHourParkingCostLinkAttributeName())).orElse(0.);
}

private double getDailyParkingCosts(Attributes attributes, double firstHourParkingCosts, double extraHourParkingCosts) {
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getDailyParkingCostLinkAttributeName())).orElse(firstHourParkingCosts + 29 * extraHourParkingCosts);
}

private double getMaxDailyParkingCosts(Attributes attributes, double dailyParkingCosts) {
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getMaxDailyParkingCostLinkAttributeName())).orElse(dailyParkingCosts);
}

private double getMaxParkingDurationHrs(Attributes attributes) {
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getMaxParkingDurationAttributeName())).orElse(30.);
}

private double getParkingPenalty(Attributes attributes) {
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getParkingPenaltyAttributeName())).orElse(0.);
}

@VisibleForTesting
Map<Id<Person>, Double> getPersonId2lastLeaveVehicleTime() {
return personId2lastLeaveVehicleTime;
}

@VisibleForTesting
Map<Id<Person>, String> getPersonId2previousActivity() {
return personId2previousActivity;
}

@VisibleForTesting
Map<Id<Person>, Id<Link>> getPersonId2relevantModeLinkId() {
return personId2relevantModeLinkId;
}

@VisibleForTesting
Set<Id<Person>> getPtDrivers() {
return ptDrivers;
}

@VisibleForTesting
Set<Id<Person>> getHasAlreadyPaidDailyResidentialParkingCosts() {
return hasAlreadyPaidDailyResidentialParkingCosts;
}
}

0 comments on commit 77f1b7a

Please sign in to comment.