Skip to content

Commit 77f1b7a

Browse files
authoredApr 10, 2024··
Merge pull request #3202 from matsim-org/refactor-parking-cost-handler
Refactor parking cost handler
2 parents 208f71f + 291eb75 commit 77f1b7a

File tree

3 files changed

+558
-129
lines changed

3 files changed

+558
-129
lines changed
 

‎contribs/vsp/src/main/java/playground/vsp/simpleParkingCostHandler/ParkingCostConfigGroup.java

-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ public class ParkingCostConfigGroup extends ReflectiveConfigGroup {
3636
public ParkingCostConfigGroup() {
3737
super(GROUP_NAME);
3838
}
39-
4039
private String mode = "car";
4140
private String dailyParkingCostLinkAttributeName = "dailyPCost";
4241
private String firstHourParkingCostLinkAttributeName = "oneHourPCost";

‎contribs/vsp/src/main/java/playground/vsp/simpleParkingCostHandler/ParkingCostHandler.java

+170-128
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@
1919

2020
package playground.vsp.simpleParkingCostHandler;
2121

22-
import java.util.HashMap;
23-
import java.util.HashSet;
24-
import java.util.Map;
25-
import java.util.Set;
22+
import java.util.*;
2623

27-
import org.apache.commons.lang.StringUtils;
24+
import com.google.common.annotations.VisibleForTesting;
2825
import org.matsim.api.core.v01.Id;
2926
import org.matsim.api.core.v01.Scenario;
3027
import org.matsim.api.core.v01.events.ActivityEndEvent;
@@ -41,39 +38,39 @@
4138
import org.matsim.api.core.v01.network.Link;
4239
import org.matsim.api.core.v01.population.Person;
4340
import org.matsim.core.api.experimental.events.EventsManager;
44-
import org.matsim.core.config.groups.QSimConfigGroup;
45-
import org.matsim.core.controler.events.AfterMobsimEvent;
46-
import org.matsim.core.controler.listener.AfterMobsimListener;
4741
import org.matsim.core.router.StageActivityTypeIdentifier;
4842

4943
import com.google.inject.Inject;
44+
import org.matsim.utils.objectattributes.attributable.Attributes;
5045

5146
/**
5247
* @author ikaddoura
5348
*/
5449

50+
/**
51+
* This class handles parking costs for different types of parking activities.
52+
* There are still things to refactor:
53+
* - There are lots of magic numbers.
54+
* - The method handleEvent(PersonEntersVehicleEvent event) is too long.
55+
* - Helpful comments are missing.
56+
* At least, there is a test class for this class. (paul april 2024)
57+
*/
5558
final class ParkingCostHandler implements TransitDriverStartsEventHandler, ActivityEndEventHandler, PersonDepartureEventHandler, PersonLeavesVehicleEventHandler, PersonEntersVehicleEventHandler {
56-
59+
5760
private final Map<Id<Person>, Double> personId2lastLeaveVehicleTime = new HashMap<>();
5861
private final Map<Id<Person>, String> personId2previousActivity = new HashMap<>();
5962
private final Map<Id<Person>, Id<Link>> personId2relevantModeLinkId = new HashMap<>();
60-
private final Map<Id<Person>, Id<Link>> personId2homeLinkId = new HashMap<>();
6163
private final Set<Id<Person>> ptDrivers = new HashSet<>();
6264
private final Set<Id<Person>> hasAlreadyPaidDailyResidentialParkingCosts = new HashSet<>();
63-
private double compensationTime = Double.NaN;
64-
65+
6566
@Inject
6667
private ParkingCostConfigGroup parkingCostConfigGroup;
67-
68+
6869
@Inject
6970
private EventsManager events;
70-
71-
@Inject
72-
private Scenario scenario;
7371

7472
@Inject
75-
private QSimConfigGroup qSimConfigGroup;
76-
73+
private Scenario scenario;
7774

7875
@Override
7976
public void reset(int iteration) {
@@ -84,143 +81,188 @@ public void reset(int iteration) {
8481
this.ptDrivers.clear();
8582
}
8683

87-
8884
@Override
8985
public void handleEvent(TransitDriverStartsEvent event) {
9086
ptDrivers.add(event.getDriverId());
9187
}
9288

93-
9489
@Override
95-
public void handleEvent(ActivityEndEvent event) {
90+
public void handleEvent(ActivityEndEvent event) {
9691
if (ptDrivers.contains(event.getPersonId())) {
97-
// skip pt drivers
98-
} else {
99-
if (!(StageActivityTypeIdentifier.isStageActivity(event.getActType()))) {
100-
101-
personId2previousActivity.put(event.getPersonId(), event.getActType());
102-
103-
if (personId2relevantModeLinkId.get(event.getPersonId()) != null) {
104-
personId2relevantModeLinkId.remove(event.getPersonId());
105-
}
106-
}
107-
}
92+
return;
93+
}
94+
if ((StageActivityTypeIdentifier.isStageActivity(event.getActType()))) {
95+
return;
96+
}
97+
personId2previousActivity.put(event.getPersonId(), event.getActType());
98+
99+
if(event.getPersonId()!= null){
100+
personId2relevantModeLinkId.remove(event.getPersonId());
101+
}
108102
}
109103

110104

111105
@Override
112106
public void handleEvent(PersonDepartureEvent event) {
113-
if (! ptDrivers.contains(event.getPersonId())) {
114-
// There might be several departures during a single trip.
115-
if (event.getLegMode().equals(parkingCostConfigGroup.getMode())) {
116-
personId2relevantModeLinkId.put(event.getPersonId(), event.getLinkId());
117-
}
107+
if (ptDrivers.contains(event.getPersonId())) {
108+
return;
118109
}
110+
111+
// There might be several departures during a single trip.
112+
if (!event.getLegMode().equals(parkingCostConfigGroup.getMode())) {
113+
return;
114+
}
115+
116+
personId2relevantModeLinkId.put(event.getPersonId(), event.getLinkId());
119117
}
120118

119+
@Override
120+
public void handleEvent(PersonLeavesVehicleEvent event) {
121+
if (ptDrivers.contains(event.getPersonId())) {
122+
return;
123+
}
124+
125+
personId2lastLeaveVehicleTime.put(event.getPersonId(), event.getTime());
126+
}
121127

122128
@Override
123129
public void handleEvent(PersonEntersVehicleEvent event) {
124-
if (! ptDrivers.contains(event.getPersonId())) {
125-
if (personId2relevantModeLinkId.get(event.getPersonId()) != null) {
126-
127-
Link link = scenario.getNetwork().getLinks().get(personId2relevantModeLinkId.get(event.getPersonId()));
128-
129-
if (parkingCostConfigGroup.getActivityPrefixesToBeExcludedFromParkingCost().stream()
130-
.noneMatch(s -> personId2previousActivity.get(event.getPersonId()).startsWith(s))){
131-
132-
if (personId2previousActivity.get(event.getPersonId()).startsWith(parkingCostConfigGroup.getActivityPrefixForDailyParkingCosts())) {
133-
// daily residential parking costs
134-
135-
if (! hasAlreadyPaidDailyResidentialParkingCosts.contains(event.getPersonId())){
136-
hasAlreadyPaidDailyResidentialParkingCosts.add(event.getPersonId());
137-
138-
double residentialParkingFeePerDay = 0.;
139-
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getResidentialParkingFeeAttributeName()) != null) {
140-
residentialParkingFeePerDay = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getResidentialParkingFeeAttributeName());
141-
}
142-
143-
if (residentialParkingFeePerDay > 0.) {
144-
double amount = -1. * residentialParkingFeePerDay;
145-
events.processEvent(new PersonMoneyEvent(event.getTime(), event.getPersonId(), amount, "residential parking", "city", "link " + link.getId().toString()));
146-
}
147-
}
148-
149-
} else {
150-
// other parking cost types
151-
152-
double parkingStartTime = 0.;
153-
if (personId2lastLeaveVehicleTime.get(event.getPersonId()) != null) {
154-
parkingStartTime = personId2lastLeaveVehicleTime.get(event.getPersonId());
155-
}
156-
int parkingDurationHrs = (int) Math.ceil((event.getTime() - parkingStartTime) / 3600.);
157-
158-
double extraHourParkingCosts = 0.;
159-
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getExtraHourParkingCostLinkAttributeName()) != null) {
160-
extraHourParkingCosts = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getExtraHourParkingCostLinkAttributeName());
161-
}
162-
163-
double firstHourParkingCosts = 0.;
164-
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getFirstHourParkingCostLinkAttributeName()) != null) {
165-
firstHourParkingCosts = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getFirstHourParkingCostLinkAttributeName());
166-
}
167-
168-
double dailyParkingCosts = firstHourParkingCosts + 29 * extraHourParkingCosts;
169-
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getDailyParkingCostLinkAttributeName()) != null) {
170-
dailyParkingCosts = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getDailyParkingCostLinkAttributeName());
171-
}
172-
173-
double maxDailyParkingCosts = dailyParkingCosts;
174-
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getMaxDailyParkingCostLinkAttributeName()) != null) {
175-
maxDailyParkingCosts = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getMaxDailyParkingCostLinkAttributeName());
176-
}
177-
178-
double maxParkingDurationHrs = 30;
179-
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getMaxParkingDurationAttributeName()) != null) {
180-
maxParkingDurationHrs = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getMaxParkingDurationAttributeName());
181-
}
182-
183-
double parkingPenalty = 0.;
184-
if (link.getAttributes().getAttribute(parkingCostConfigGroup.getParkingPenaltyAttributeName()) != null) {
185-
parkingPenalty = (double) link.getAttributes().getAttribute(parkingCostConfigGroup.getParkingPenaltyAttributeName());
186-
}
187-
188-
double costs = 0.;
189-
if (parkingDurationHrs > 0) {
190-
costs += firstHourParkingCosts;
191-
costs += (parkingDurationHrs - 1) * extraHourParkingCosts;
192-
}
193-
if (costs > dailyParkingCosts) {
194-
costs = dailyParkingCosts;
195-
}
196-
if (costs > maxDailyParkingCosts) {
197-
costs = maxDailyParkingCosts;
198-
}
199-
if ((parkingDurationHrs > maxParkingDurationHrs) & (costs < parkingPenalty)) {
200-
costs = parkingPenalty;
201-
}
202-
203-
if (costs > 0.) {
204-
double amount = -1. * costs;
205-
events.processEvent(new PersonMoneyEvent(event.getTime(), event.getPersonId(), amount, "non-residential parking", "city", "link " + link.getId().toString()));
206-
}
207-
208-
}
209-
210-
}
130+
// Preliminaries
131+
if (ptDrivers.contains(event.getPersonId())) {
132+
return;
133+
}
134+
if (personId2relevantModeLinkId.get(event.getPersonId()) == null) {
135+
return;
136+
}
211137

138+
if (parkingCostConfigGroup.getActivityPrefixesToBeExcludedFromParkingCost().stream()
139+
.anyMatch(s -> personId2previousActivity.get(event.getPersonId()).startsWith(s))) {
140+
return;
141+
}
142+
143+
Link link = scenario.getNetwork().getLinks().get(personId2relevantModeLinkId.get(event.getPersonId()));
144+
Attributes attributes = link.getAttributes();
145+
146+
if (personId2previousActivity.get(event.getPersonId()).startsWith(parkingCostConfigGroup.getActivityPrefixForDailyParkingCosts())) {
147+
// daily residential parking costs
148+
if (hasAlreadyPaidDailyResidentialParkingCosts.contains(event.getPersonId())){
149+
// has already paid daily residential parking costs
150+
return;
151+
}
152+
hasAlreadyPaidDailyResidentialParkingCosts.add(event.getPersonId());
153+
154+
double residentialParkingFeePerDay = getResidentialParkingFeePerDay(attributes);
155+
156+
if (residentialParkingFeePerDay > 0.) {
157+
double amount = -1. * residentialParkingFeePerDay;
158+
events.processEvent(createPersonMoneyEvent(event, amount, link, "residential parking"));
212159
}
160+
return;
213161
}
214162

163+
// other parking cost types
164+
165+
double costs = calculateNonResidentialParkingCosts(event, attributes);
166+
167+
if (costs <= 0.) {
168+
return;
169+
}
170+
double amount = -1. * costs;
171+
events.processEvent(createPersonMoneyEvent(event, amount, link, "non-residential parking"));
215172
}
216173

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

218-
@Override
219-
public void handleEvent(PersonLeavesVehicleEvent event) {
220-
if (! ptDrivers.contains(event.getPersonId())) {
221-
personId2lastLeaveVehicleTime.put(event.getPersonId(), event.getTime());
178+
private double calculateNonResidentialParkingCosts(PersonEntersVehicleEvent event, Attributes attributes) {
179+
double costs = 0.;
180+
181+
int parkingDurationHrs = (int) Math.ceil((event.getTime() - getParkingStartTime(event)) / 3600.);
182+
double firstHourParkingCosts = getFirstHourParkingCosts(attributes);
183+
double extraHourParkingCosts = getExtraHourParkingCosts(attributes);
184+
185+
if (parkingDurationHrs > 0) {
186+
costs += firstHourParkingCosts;
187+
costs += (parkingDurationHrs - 1) * extraHourParkingCosts;
188+
}
189+
190+
double dailyParkingCosts = getDailyParkingCosts(attributes, firstHourParkingCosts, extraHourParkingCosts);
191+
192+
if (costs > dailyParkingCosts) {
193+
costs = dailyParkingCosts;
194+
}
195+
196+
double maxDailyParkingCosts = getMaxDailyParkingCosts(attributes, dailyParkingCosts);
197+
198+
if (costs > maxDailyParkingCosts) {
199+
costs = maxDailyParkingCosts;
222200
}
201+
202+
double maxParkingDurationHrs = getMaxParkingDurationHrs(attributes);
203+
double parkingPenalty = getParkingPenalty(attributes);
204+
205+
if ((parkingDurationHrs > maxParkingDurationHrs) & (costs < parkingPenalty)) {
206+
costs = parkingPenalty;
207+
}
208+
return costs;
209+
}
210+
211+
private double getResidentialParkingFeePerDay(Attributes attributes) {
212+
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getResidentialParkingFeeAttributeName())).orElse(0.);
213+
}
214+
215+
private double getParkingStartTime(PersonEntersVehicleEvent event) {
216+
return Optional.ofNullable(personId2lastLeaveVehicleTime.get(event.getPersonId())).orElse(0.);
217+
}
218+
219+
private double getExtraHourParkingCosts(Attributes attributes) {
220+
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getExtraHourParkingCostLinkAttributeName())).orElse(0.);
223221
}
224222

223+
private double getFirstHourParkingCosts(Attributes attributes) {
224+
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getFirstHourParkingCostLinkAttributeName())).orElse(0.);
225+
}
226+
227+
private double getDailyParkingCosts(Attributes attributes, double firstHourParkingCosts, double extraHourParkingCosts) {
228+
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getDailyParkingCostLinkAttributeName())).orElse(firstHourParkingCosts + 29 * extraHourParkingCosts);
229+
}
230+
231+
private double getMaxDailyParkingCosts(Attributes attributes, double dailyParkingCosts) {
232+
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getMaxDailyParkingCostLinkAttributeName())).orElse(dailyParkingCosts);
233+
}
234+
235+
private double getMaxParkingDurationHrs(Attributes attributes) {
236+
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getMaxParkingDurationAttributeName())).orElse(30.);
237+
}
238+
239+
private double getParkingPenalty(Attributes attributes) {
240+
return (double) Optional.ofNullable(attributes.getAttribute(parkingCostConfigGroup.getParkingPenaltyAttributeName())).orElse(0.);
241+
}
242+
243+
@VisibleForTesting
244+
Map<Id<Person>, Double> getPersonId2lastLeaveVehicleTime() {
245+
return personId2lastLeaveVehicleTime;
246+
}
247+
248+
@VisibleForTesting
249+
Map<Id<Person>, String> getPersonId2previousActivity() {
250+
return personId2previousActivity;
251+
}
252+
253+
@VisibleForTesting
254+
Map<Id<Person>, Id<Link>> getPersonId2relevantModeLinkId() {
255+
return personId2relevantModeLinkId;
256+
}
257+
258+
@VisibleForTesting
259+
Set<Id<Person>> getPtDrivers() {
260+
return ptDrivers;
261+
}
262+
263+
@VisibleForTesting
264+
Set<Id<Person>> getHasAlreadyPaidDailyResidentialParkingCosts() {
265+
return hasAlreadyPaidDailyResidentialParkingCosts;
266+
}
225267
}
226268

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
package playground.vsp.simpleParkingCostHandler;
2+
3+
import com.google.inject.AbstractModule;
4+
import com.google.inject.Guice;
5+
import com.google.inject.Injector;
6+
import org.junit.jupiter.api.*;
7+
import org.matsim.api.core.v01.Coord;
8+
import org.matsim.api.core.v01.Id;
9+
import org.matsim.api.core.v01.Scenario;
10+
import org.matsim.api.core.v01.events.*;
11+
import org.matsim.api.core.v01.network.Link;
12+
import org.matsim.api.core.v01.network.Network;
13+
import org.matsim.api.core.v01.network.Node;
14+
import org.matsim.api.core.v01.population.Person;
15+
import org.matsim.api.core.v01.population.Plan;
16+
import org.matsim.core.api.experimental.events.EventsManager;
17+
import org.matsim.core.config.Config;
18+
import org.matsim.core.config.ConfigUtils;
19+
import org.matsim.core.controler.OutputDirectoryHierarchy;
20+
import org.matsim.core.events.handler.EventHandler;
21+
import org.matsim.core.network.NetworkUtils;
22+
import org.matsim.core.scenario.ScenarioUtils;
23+
import org.matsim.utils.objectattributes.attributable.Attributes;
24+
25+
import java.util.*;
26+
27+
/**
28+
* Tests for the ParkingCostHandler class.
29+
* The following test cases could be added:
30+
* - Test chain of parking events.
31+
* - Test activity prefixes excluded from parking.
32+
*/
33+
public class ParkingCostHandlerTest {
34+
private Injector injector;
35+
36+
@BeforeEach
37+
public void setup() {
38+
Config config = ConfigUtils.createConfig();
39+
config.controller().setOverwriteFileSetting(OutputDirectoryHierarchy.OverwriteFileSetting.deleteDirectoryIfExists);
40+
Scenario scenario = ScenarioUtils.createScenario(config);
41+
Network network = NetworkUtils.createNetwork(config);
42+
43+
TestsEventsManager eventsManager = new TestsEventsManager();
44+
ParkingCostConfigGroup configGroup = new ParkingCostConfigGroup();
45+
46+
injector = Guice.createInjector(new AbstractModule() {
47+
@Override
48+
public void configure() {
49+
bind(ParkingCostConfigGroup.class).toInstance(configGroup);
50+
bind(EventsManager.class).toInstance(eventsManager);
51+
bind(Scenario.class).toInstance(scenario);
52+
bind(Network.class).toInstance(network);
53+
}
54+
});
55+
}
56+
// Basic Event Handling Tests
57+
@Test
58+
public void transitDriverStartsEventTest() {
59+
Person ptDriver = new Tester(0);
60+
TransitDriverStartsEvent tDSE = new TransitDriverStartsEvent(0, ptDriver.getId(), null, null, null, null);
61+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
62+
pch.handleEvent(tDSE);
63+
Assertions.assertTrue(pch.getPtDrivers().contains(ptDriver.getId()));
64+
}
65+
66+
@Test
67+
public void personLeavesVehicleEventTest() {
68+
Person ptDriver = new Tester(0);
69+
Person tester = new Tester(1);
70+
71+
TransitDriverStartsEvent tDSEvent = new TransitDriverStartsEvent(0, ptDriver.getId(), null, null, null, null);
72+
PersonLeavesVehicleEvent pLVEventParking = new PersonLeavesVehicleEvent(100, tester.getId(), null);
73+
PersonLeavesVehicleEvent pLVEventPtDriver = new PersonLeavesVehicleEvent(100, ptDriver.getId(), null);
74+
75+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
76+
77+
pch.handleEvent(tDSEvent);
78+
pch.handleEvent(pLVEventParking);
79+
pch.handleEvent(pLVEventPtDriver);
80+
81+
Assertions.assertTrue(pch.getPtDrivers().contains(ptDriver.getId()));
82+
Assertions.assertFalse(pch.getPersonId2lastLeaveVehicleTime().containsKey(ptDriver.getId()));
83+
Assertions.assertTrue(pch.getPersonId2lastLeaveVehicleTime().containsKey(tester.getId()));
84+
Assertions.assertEquals(100.,pch.getPersonId2lastLeaveVehicleTime().get(tester.getId()));
85+
}
86+
@Test
87+
public void activityEndEventTest() {
88+
Person tester1 = new Tester(0);
89+
String actType1 = "test";
90+
91+
Person tester2 = new Tester(1);
92+
String actType2 = "test-interaction";
93+
94+
Person ptDriver = new Tester(2);
95+
96+
ActivityEndEvent aEEventActivity = new ActivityEndEvent(0, tester1.getId(), null, null,actType1, null);
97+
ActivityEndEvent aEEventInteraction = new ActivityEndEvent(0, tester2.getId(), null, null, actType2, null);
98+
TransitDriverStartsEvent tDSEvent = new TransitDriverStartsEvent(0, ptDriver.getId(), null, null, null, null);
99+
100+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
101+
102+
pch.handleEvent(tDSEvent);
103+
pch.handleEvent(aEEventActivity);
104+
pch.handleEvent(aEEventInteraction);
105+
106+
Assertions.assertNull(pch.getPersonId2previousActivity().get(tester2.getId()));
107+
Assertions.assertEquals(actType1, pch.getPersonId2previousActivity().get(tester1.getId()));
108+
109+
Assertions.assertFalse(pch.getPersonId2previousActivity().containsKey(ptDriver.getId()));
110+
Assertions.assertTrue(pch.getPtDrivers().contains(ptDriver.getId()));
111+
}
112+
@Test
113+
public void personDepartureEventTest() {
114+
Person testerWithCar = new Tester(0);
115+
Person testerWithoutCar = new Tester(1);
116+
Person ptDriver = new Tester(2);
117+
118+
PersonDepartureEvent pDEventCar = new PersonDepartureEvent(0, testerWithCar.getId(),null, "car",null);
119+
PersonDepartureEvent pDEventNoCar = new PersonDepartureEvent(0, testerWithoutCar.getId(), null, "pt", null);
120+
PersonDepartureEvent pDEventPtDriver = new PersonDepartureEvent(0, ptDriver.getId(),null,"pt",null);
121+
TransitDriverStartsEvent tDSEvent = new TransitDriverStartsEvent(0, ptDriver.getId(), null, null, null, null);
122+
123+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
124+
125+
pch.handleEvent(tDSEvent);
126+
pch.handleEvent(pDEventCar);
127+
pch.handleEvent(pDEventNoCar);
128+
pch.handleEvent(pDEventPtDriver);
129+
130+
Assertions.assertTrue(pch.getPtDrivers().contains(ptDriver.getId()));
131+
Assertions.assertFalse(pch.getPersonId2relevantModeLinkId().containsKey(ptDriver.getId()));
132+
Assertions.assertFalse(pch.getPersonId2relevantModeLinkId().containsKey(testerWithoutCar.getId()));
133+
Assertions.assertTrue(pch.getPersonId2relevantModeLinkId().containsKey(testerWithCar.getId()));
134+
}
135+
@Test
136+
public void personEntersVehicleEventTest() {
137+
// tests basic functionality and cost calculation
138+
Link link = createNetworkAndSetupLink(10,440,5,15,500,30,30);
139+
// Persons construction
140+
Person ptDriver = new Tester(0); // should not pay at all
141+
Person resident = new Tester(1); // should pay residential parking costs
142+
Person shopper = new Tester(2); // should pay non-residential parking costs
143+
// Event construction
144+
// ptDriver events
145+
TransitDriverStartsEvent tDSEvent = new TransitDriverStartsEvent(0, ptDriver.getId(), null, null, null, null);
146+
PersonLeavesVehicleEvent pLVEventPtDriver = new PersonLeavesVehicleEvent(1000, ptDriver.getId(), null);
147+
PersonEntersVehicleEvent pEVEventPtDriver = new PersonEntersVehicleEvent(11000, ptDriver.getId(),null);
148+
// resident events
149+
List<Event> residentEvents = createParkingEvents(resident.getId(),link.getId(),"home","car",10);
150+
// shopper events
151+
List<Event> shopperEvents = createParkingEvents(shopper.getId(),link.getId(),"shopping","car",1);
152+
153+
// Event handling
154+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
155+
// ptDriver handling
156+
pch.handleEvent(tDSEvent);
157+
pch.handleEvent(pLVEventPtDriver);
158+
pch.handleEvent(pEVEventPtDriver);
159+
// resident handling
160+
handleParkingEvents(pch, residentEvents);
161+
// shopper handling
162+
handleParkingEvents(pch, shopperEvents);
163+
164+
// Assertions
165+
TestsEventsManager eventsManager = (TestsEventsManager) injector.getInstance(EventsManager.class);
166+
// ptDriver assertions
167+
Assertions.assertTrue(pch.getPtDrivers().contains(ptDriver.getId()));
168+
Assertions.assertNull(eventsManager.getEventByPersonId(ptDriver.getId()));
169+
// resident assertions
170+
Assertions.assertTrue(pch.getHasAlreadyPaidDailyResidentialParkingCosts().contains(resident.getId()));
171+
Assertions.assertEquals(-10.0, eventsManager.getEventByPersonId(resident.getId()).getAmount());
172+
Assertions.assertEquals("residential parking", eventsManager.getEventByPersonId(resident.getId()).getPurpose());
173+
// shopper assertions
174+
Assertions.assertFalse(pch.getHasAlreadyPaidDailyResidentialParkingCosts().contains(shopper.getId()));
175+
Assertions.assertEquals(-5, eventsManager.getEventByPersonId(shopper.getId()).getAmount());
176+
Assertions.assertEquals("non-residential parking", eventsManager.getEventByPersonId(shopper.getId()).getPurpose());
177+
178+
}
179+
// Parking Cost Calculation Tests
180+
@Test
181+
public void penaltyParkingCostCalculationsTest() {
182+
// Network construction
183+
Link link = createNetworkAndSetupLink(0, 200,10,10,200,2,100);
184+
// Person construction
185+
Person tester1 = new Tester(0);
186+
Person tester2 = new Tester(1);
187+
// Event construction
188+
List<Event> events = createParkingEvents(tester1.getId(),link.getId(),"other","car",3);
189+
// Event handling
190+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
191+
handleParkingEvents(pch, events);
192+
handleParkingEvents(pch, createParkingEvents(tester2.getId(),link.getId(),"other","car",20));
193+
// Assertions
194+
TestsEventsManager eventsManager = (TestsEventsManager) injector.getInstance(EventsManager.class);
195+
Assertions.assertEquals(-100, eventsManager.getEventByPersonId(tester1.getId()).getAmount());
196+
Assertions.assertEquals(-200, eventsManager.getEventByPersonId(tester2.getId()).getAmount());
197+
}
198+
@Test
199+
public void firstHourParkingCostCalculationTest() {
200+
Link link = createNetworkAndSetupLink(0, 50,10,10,100,10,0);
201+
202+
Person tester1 = new Tester(0);
203+
Person tester2 = new Tester(1);
204+
205+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
206+
handleParkingEvents(pch, createParkingEvents(tester1.getId(),link.getId(),"other","car",1));
207+
handleParkingEvents(pch, createParkingEvents(tester2.getId(),link.getId(),"other","car",0.5)); // rounded up to 1hr
208+
209+
TestsEventsManager eventsManager = (TestsEventsManager) injector.getInstance(EventsManager.class);
210+
Assertions.assertEquals(-10, eventsManager.getEventByPersonId(tester1.getId()).getAmount());
211+
Assertions.assertEquals(-10, eventsManager.getEventByPersonId(tester2.getId()).getAmount());
212+
}
213+
@Test
214+
public void hourlyParkingCostCalculationTest() {
215+
Link link = createNetworkAndSetupLink(0, 150,10,20,100,10,0);
216+
217+
Person parking1hr = new Tester(0);
218+
Person parking3hr = new Tester(1);
219+
Person parking8hr = new Tester(2);
220+
221+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
222+
handleParkingEvents(pch, createParkingEvents(parking1hr.getId(),link.getId(),"other","car",1));
223+
handleParkingEvents(pch, createParkingEvents(parking3hr.getId(),link.getId(),"other","car",3));
224+
handleParkingEvents(pch, createParkingEvents(parking8hr.getId(),link.getId(),"other","car",8));
225+
226+
TestsEventsManager eventsManager = (TestsEventsManager) injector.getInstance(EventsManager.class);
227+
Assertions.assertEquals(-10, eventsManager.getEventByPersonId(parking1hr.getId()).getAmount()); // first hour parking cost only
228+
Assertions.assertEquals(-50, eventsManager.getEventByPersonId(parking3hr.getId()).getAmount()); // 1x 10 + 2x 20 hourly costs
229+
Assertions.assertEquals(-100, eventsManager.getEventByPersonId(parking8hr.getId()).getAmount()); // 150 in hourly costs, capped by maxDailyParkingCosts
230+
}
231+
@Test
232+
public void residentialFeeIsPaidOnceTest() {
233+
Link link = createNetworkAndSetupLink(100, 0,0,0,0,0,0);
234+
235+
Person tester = new Tester(0);
236+
237+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
238+
handleParkingEvents(pch, createParkingEvents(tester.getId(),link.getId(),"home","car",10));
239+
handleParkingEvents(pch, createParkingEvents(tester.getId(),link.getId(),"home","car",2));
240+
241+
TestsEventsManager eventsManager = (TestsEventsManager) injector.getInstance(EventsManager.class);
242+
Assertions.assertEquals(1, eventsManager.getEvents().size());
243+
Assertions.assertEquals(-100, eventsManager.getEventByPersonId(tester.getId()).getAmount());
244+
}
245+
246+
@Test
247+
public void handlerDefaultsTest() {
248+
Network network = injector.getInstance(Scenario.class).getNetwork();
249+
Node node1 = NetworkUtils.createAndAddNode(network, Id.createNodeId(1), new Coord(0, 0));
250+
Node node2 = NetworkUtils.createAndAddNode(network, Id.createNodeId(2), new Coord(10, 0));
251+
Link link = NetworkUtils.createAndAddLink(network, Id.createLinkId(1), node1, node2,10,30,100,3);
252+
253+
Person tester = new Tester(0);
254+
255+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
256+
handleParkingEvents(pch, createParkingEvents(tester.getId(),link.getId(),"other","car",10));
257+
258+
TestsEventsManager eventsManager = (TestsEventsManager) injector.getInstance(EventsManager.class);
259+
Assertions.assertTrue(eventsManager.getEvents().isEmpty());
260+
}
261+
@Test
262+
public void testDailyParkingCost() {
263+
Link link = createNetworkAndSetupLink(0, 50,10,10,100,10,0);
264+
265+
Person tester1 = new Tester(0);
266+
Person tester2 = new Tester(1);
267+
268+
ParkingCostHandler pch = injector.getInstance(ParkingCostHandler.class);
269+
handleParkingEvents(pch, createParkingEvents(tester1.getId(),link.getId(),"other","car",10));
270+
handleParkingEvents(pch, createParkingEvents(tester2.getId(),link.getId(),"other","car",2));
271+
272+
TestsEventsManager eventsManager = (TestsEventsManager) injector.getInstance(EventsManager.class);
273+
Assertions.assertEquals(-50, eventsManager.getEventByPersonId(tester1.getId()).getAmount());
274+
Assertions.assertEquals(-20, eventsManager.getEventByPersonId(tester2.getId()).getAmount());
275+
}
276+
277+
// sets up given cost attributes for a given link
278+
private Link createNetworkAndSetupLink(double residentialParkingFee, double dailyParkingCost, double firstHourParkingCost,
279+
double extraHourParkingCost, double maxDailyParkingCost, double maxParkingDurationHrs, double parkingPenaltyCost) {
280+
281+
Network network = injector.getInstance(Scenario.class).getNetwork();
282+
Node node1 = NetworkUtils.createAndAddNode(network, Id.createNodeId(1), new Coord(0, 0));
283+
Node node2 = NetworkUtils.createAndAddNode(network, Id.createNodeId(2), new Coord(10, 0));
284+
Link link = NetworkUtils.createAndAddLink(network, Id.createLinkId(1), node1, node2,10,30,100,3);
285+
286+
ParkingCostConfigGroup configGroup = injector.getInstance(ParkingCostConfigGroup.class);
287+
288+
link.getAttributes().putAttribute(configGroup.getResidentialParkingFeeAttributeName(), residentialParkingFee);
289+
link.getAttributes().putAttribute(configGroup.getDailyParkingCostLinkAttributeName(), dailyParkingCost);
290+
link.getAttributes().putAttribute(configGroup.getFirstHourParkingCostLinkAttributeName(), firstHourParkingCost);
291+
link.getAttributes().putAttribute(configGroup.getExtraHourParkingCostLinkAttributeName(), extraHourParkingCost);
292+
link.getAttributes().putAttribute(configGroup.getMaxDailyParkingCostLinkAttributeName(), maxDailyParkingCost);
293+
link.getAttributes().putAttribute(configGroup.getMaxParkingDurationAttributeName(), maxParkingDurationHrs);
294+
link.getAttributes().putAttribute(configGroup.getParkingPenaltyAttributeName(), parkingPenaltyCost);
295+
296+
return link;
297+
}
298+
299+
private List<Event> createParkingEvents(Id<Person> personId, Id<Link> linkId, String actType, String mode, double durationHrs){
300+
List<Event> events = new ArrayList<>();
301+
events.add(new PersonLeavesVehicleEvent(0, personId, null));
302+
events.add(new ActivityEndEvent(0, personId, linkId,null, actType,null));
303+
events.add(new PersonDepartureEvent(0, personId, linkId, mode,null));
304+
events.add(new PersonEntersVehicleEvent(durationHrs * 3600, personId,null));
305+
return events;
306+
}
307+
308+
private void handleParkingEvents(ParkingCostHandler pch, List<Event> events) {
309+
for (Event event : events) {
310+
if (event instanceof PersonLeavesVehicleEvent) {
311+
pch.handleEvent((PersonLeavesVehicleEvent) event);
312+
}
313+
if (event instanceof ActivityEndEvent) {
314+
pch.handleEvent((ActivityEndEvent) event);
315+
}
316+
if (event instanceof PersonDepartureEvent) {
317+
pch.handleEvent((PersonDepartureEvent) event);
318+
}
319+
if (event instanceof PersonEntersVehicleEvent) {
320+
pch.handleEvent((PersonEntersVehicleEvent) event);
321+
}
322+
}
323+
}
324+
}
325+
class TestsEventsManager implements EventsManager {
326+
private final List<PersonMoneyEvent> events = new ArrayList<>();
327+
328+
@Override
329+
public void processEvent(Event event) {
330+
if (event instanceof PersonMoneyEvent) {
331+
events.add((PersonMoneyEvent) event);
332+
}
333+
}
334+
public List<PersonMoneyEvent> getEvents() {
335+
return events;
336+
}
337+
338+
public PersonMoneyEvent getEventByPersonId(Id<Person> id) {
339+
340+
List<PersonMoneyEvent> personMoneyEvents = events.stream().filter(e -> e.getPersonId().equals(id)).toList();
341+
342+
if (personMoneyEvents.size() > 1) {
343+
Assertions.fail("Person has more money events than expected");
344+
}
345+
346+
return personMoneyEvents.stream().findAny().orElse(null);
347+
}
348+
@Deprecated
349+
public void addHandler(EventHandler handler) {}
350+
@Deprecated
351+
public void removeHandler(EventHandler handler) {}
352+
@Deprecated
353+
public void resetHandlers(int iteration) {}
354+
@Deprecated
355+
public void initProcessing() {}
356+
@Deprecated
357+
public void afterSimStep(double time) {}
358+
@Deprecated
359+
public void finishProcessing() {}
360+
}
361+
362+
class Tester implements Person {
363+
364+
public Tester(long key) {
365+
this.id = Id.createPersonId(key);
366+
}
367+
368+
private final Id<Person> id;
369+
370+
@Override
371+
public Id<Person> getId() {return this.id;}
372+
@Deprecated
373+
public Attributes getAttributes() {return null;}
374+
@Deprecated
375+
public Map<String, Object> getCustomAttributes() {return null;}
376+
@Deprecated
377+
public List<? extends Plan> getPlans() {return null;}
378+
@Deprecated
379+
public boolean addPlan(Plan p) {return false;}
380+
@Deprecated
381+
public boolean removePlan(Plan p) {return false;}
382+
@Deprecated
383+
public Plan getSelectedPlan() {return null;}
384+
@Deprecated
385+
public void setSelectedPlan(Plan selectedPlan) {}
386+
@Deprecated
387+
public Plan createCopyOfSelectedPlanAndMakeSelected() {return null;}
388+
}

0 commit comments

Comments
 (0)
Please sign in to comment.