Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for park and ride itineraries when calculating CO2 emissions #5489

Merged
merged 10 commits into from
Nov 13, 2023
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.opentripplanner.ext.emissions;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.opentripplanner.transit.model._data.TransitModelForTest.id;

import java.time.OffsetDateTime;
Expand All @@ -9,76 +10,60 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.opentripplanner._support.time.ZoneIds;
import org.opentripplanner.framework.model.Grams;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.model.plan.ScheduledTransitLeg;
import org.opentripplanner.model.plan.ScheduledTransitLegBuilder;
import org.opentripplanner.model.plan.StreetLeg;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.transit.model._data.TransitModelForTest;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.model.timetable.TripTimes;

class EmissionsTest {

private DefaultEmissionsService eService;
private EmissionsFilter emissionsFilter;
private static DefaultEmissionsService eService;
private static EmissionsFilter emissionsFilter;

static final ZonedDateTime TIME = OffsetDateTime
.parse("2023-07-20T17:49:06+03:00")
.toZonedDateTime();

private static final Agency subject = Agency
.of(TransitModelForTest.id("F:1"))
.withName("Foo_CO")
.withTimezone("Europe/Helsinki")
private static final StreetLeg STREET_LEG = StreetLeg
.create()
.withMode(TraverseMode.CAR)
.withDistanceMeters(214.4)
.withStartTime(TIME)
.withEndTime(TIME.plusHours(1))
.build();

@BeforeEach
void SetUp() {
private static final Route ROUTE_WITH_EMISSIONS = TransitModelForTest.route(id("1")).build();
private static final Route ROUTE_WITH_ZERO_EMISSIONS = TransitModelForTest.route(id("2")).build();
private static final Route ROUTE_WITHOUT_EMISSIONS_CONFIGURED = TransitModelForTest
.route(id("3"))
.build();

@BeforeAll
static void SetUp() {
Map<FeedScopedId, Double> emissions = new HashMap<>();
emissions.put(new FeedScopedId("F", "1"), (0.12 / 12));
emissions.put(new FeedScopedId("F", "2"), 0.0);
EmissionsDataModel emissionsDataModel = new EmissionsDataModel(emissions, 0.131);
this.eService = new DefaultEmissionsService(emissionsDataModel);
this.emissionsFilter = new EmissionsFilter(eService);
eService = new DefaultEmissionsService(emissionsDataModel);
emissionsFilter = new EmissionsFilter(eService);
}

@Test
void testGetEmissionsForItinerary() {
var testModel = TransitModelForTest.of();
var stopOne = testModel.stop("1:stop1", 60, 25).build();
var stopTwo = testModel.stop("1:stop1", 61, 25).build();
var stopThree = testModel.stop("1:stop1", 62, 25).build();
var stopPattern = TransitModelForTest.stopPattern(stopOne, stopTwo, stopThree);
var route = TransitModelForTest.route(id("1")).build();
var pattern = TransitModelForTest.tripPattern("1", route).withStopPattern(stopPattern).build();
var stoptime = new StopTime();
var stoptimes = new ArrayList<StopTime>();
stoptimes.add(stoptime);
var trip = Trip
.of(FeedScopedId.parse("FOO:BAR"))
.withMode(TransitMode.BUS)
.withRoute(route)
.build();
var leg = new ScheduledTransitLegBuilder<>()
.withTripTimes(new TripTimes(trip, stoptimes, new Deduplicator()))
.withTripPattern(pattern)
.withBoardStopIndexInPattern(0)
.withAlightStopIndexInPattern(2)
.withStartTime(TIME)
.withEndTime(TIME.plusMinutes(10))
.withServiceDate(TIME.toLocalDate())
.withZoneId(ZoneIds.BERLIN)
.build();
Itinerary i = new Itinerary(List.of(leg));
Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS)));
assertEquals(
new Grams(2223.902),
emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2()
Expand All @@ -87,14 +72,7 @@ void testGetEmissionsForItinerary() {

@Test
void testGetEmissionsForCarRoute() {
var leg = StreetLeg
.create()
.withMode(TraverseMode.CAR)
.withDistanceMeters(214.4)
.withStartTime(TIME)
.withEndTime(TIME.plusHours(1))
.build();
Itinerary i = new Itinerary(List.of(leg));
Itinerary i = new Itinerary(List.of(STREET_LEG));
assertEquals(
new Grams(28.0864),
emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2()
Expand All @@ -103,60 +81,56 @@ void testGetEmissionsForCarRoute() {

@Test
void testNoEmissionsForFeedWithoutEmissionsConfigured() {
var testModel = TransitModelForTest.of();
Map<FeedScopedId, Double> emissions = new HashMap<>();
emissions.put(new FeedScopedId("G", "1"), (0.12 / 12));
EmissionsDataModel emissionsDataModel = new EmissionsDataModel(emissions, 0.131);
Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED)));
assertNull(emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson());
}

this.eService = new DefaultEmissionsService(emissionsDataModel);
this.emissionsFilter = new EmissionsFilter(this.eService);
@Test
void testZeroEmissionsForItineraryWithZeroEmissions() {
Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_ZERO_EMISSIONS)));
assertEquals(
new Grams(0.0),
emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2()
);
}

var route = TransitModelForTest.route(id("1")).withAgency(subject).build();
var pattern = TransitModelForTest
.tripPattern("1", route)
.withStopPattern(testModel.stopPattern(3))
.build();
var stoptime = new StopTime();
var stoptimes = new ArrayList<StopTime>();
stoptimes.add(stoptime);
var trip = Trip
.of(FeedScopedId.parse("FOO:BAR"))
.withMode(TransitMode.BUS)
.withRoute(route)
.build();
var leg = new ScheduledTransitLegBuilder<>()
.withTripTimes(new TripTimes(trip, stoptimes, new Deduplicator()))
.withTripPattern(pattern)
.withBoardStopIndexInPattern(0)
.withAlightStopIndexInPattern(2)
.withStartTime(TIME)
.withEndTime(TIME.plusMinutes(10))
.withServiceDate(TIME.toLocalDate())
.withZoneId(ZoneIds.BERLIN)
.build();
Itinerary i = new Itinerary(List.of(leg));
assertEquals(null, emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson());
@Test
void testGetEmissionsForCombinedRoute() {
Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS), STREET_LEG));
assertEquals(
new Grams(2251.9884),
emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2()
);
}

@Test
void testZeroEmissionsForItineraryWithZeroEmissions() {
void testNoEmissionsForCombinedRouteWithoutTransitEmissions() {
Itinerary i = new Itinerary(
List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED), STREET_LEG)
);
var emissionsResult = emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson() != null
? emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2()
: null;
assertNull(emissionsResult);
}

private ScheduledTransitLeg createTransitLeg(Route route) {
var stoptime = new StopTime();
var stopTimes = new ArrayList<StopTime>();
stopTimes.add(stoptime);
var testModel = TransitModelForTest.of();
var stopOne = testModel.stop("1:stop1", 60, 25).build();
var stopTwo = testModel.stop("1:stop1", 61, 25).build();
var stopThree = testModel.stop("1:stop1", 62, 25).build();
var stopPattern = TransitModelForTest.stopPattern(stopOne, stopTwo, stopThree);
var route = TransitModelForTest.route(id("2")).build();
var pattern = TransitModelForTest.tripPattern("1", route).withStopPattern(stopPattern).build();
var stoptime = new StopTime();
var stoptimes = new ArrayList<StopTime>();
stoptimes.add(stoptime);
var trip = Trip
.of(FeedScopedId.parse("FOO:BAR"))
.withMode(TransitMode.BUS)
.withRoute(route)
.build();
var leg = new ScheduledTransitLegBuilder<>()
.withTripTimes(new TripTimes(trip, stoptimes, new Deduplicator()))
return new ScheduledTransitLegBuilder<>()
.withTripTimes(new TripTimes(trip, stopTimes, new Deduplicator()))
.withTripPattern(pattern)
.withBoardStopIndexInPattern(0)
.withAlightStopIndexInPattern(2)
Expand All @@ -165,10 +139,5 @@ void testZeroEmissionsForItineraryWithZeroEmissions() {
.withServiceDate(TIME.toLocalDate())
.withZoneId(ZoneIds.BERLIN)
.build();
Itinerary i = new Itinerary(List.of(leg));
assertEquals(
new Grams(0.0),
emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ public List<Itinerary> filter(List<Itinerary> itineraries) {
.map(TransitLeg.class::cast)
.toList();

calculateCo2EmissionsForTransit(transitLegs)
.ifPresent(co2 -> {
itinerary.setEmissionsPerPerson(new Emissions(co2));
});
Optional<Grams> co2ForTransit = calculateCo2EmissionsForTransit(transitLegs);

if (!transitLegs.isEmpty() && co2ForTransit.isEmpty()) {
continue;
}

List<StreetLeg> carLegs = itinerary
.getLegs()
Expand All @@ -43,10 +44,15 @@ public List<Itinerary> filter(List<Itinerary> itineraries) {
.filter(leg -> leg.getMode() == TraverseMode.CAR)
.toList();

calculateCo2EmissionsForCar(carLegs)
.ifPresent(co2 -> {
itinerary.setEmissionsPerPerson(new Emissions(co2));
});
Optional<Grams> co2ForCar = calculateCo2EmissionsForCar(carLegs);

if (co2ForTransit.isPresent() && co2ForCar.isPresent()) {
itinerary.setEmissionsPerPerson(new Emissions(co2ForTransit.get().plus(co2ForCar.get())));
} else if (co2ForTransit.isPresent()) {
itinerary.setEmissionsPerPerson(new Emissions(co2ForTransit.get()));
} else if (co2ForCar.isPresent()) {
itinerary.setEmissionsPerPerson(new Emissions(co2ForCar.get()));
}
}
return itineraries;
}
Expand Down