From 1faf8a0b5f0528163222a78b23cde9bfffded410 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 16 Jun 2023 11:49:17 +0300 Subject: [PATCH 01/68] load co2 data for digitransit --- docs/BuildConfiguration.md | 14 ++++ docs/Configuration.md | 1 + .../ext/emissions/DigitransitEmissions.java | 75 +++++++++++++++++++ .../DigitransitEmissionsService.java | 27 +++++++ .../ext/emissions/EmissionsService.java | 5 ++ .../emissions/EmissionsConfig.java | 30 ++++++++ .../emissions/EmissionsModule.java | 73 ++++++++++++++++++ .../framework/application/OTPFeature.java | 2 + .../graph_builder/GraphBuilder.java | 5 +- .../module/configure/GraphBuilderFactory.java | 4 + .../module/configure/GraphBuilderModules.java | 13 ++++ .../standalone/config/BuildConfig.java | 4 + .../transit/service/TransitModel.java | 11 +++ .../standalone/config/build-config.json | 5 +- 14 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java create mode 100644 src/main/java/org/opentripplanner/emissions/EmissionsConfig.java create mode 100644 src/main/java/org/opentripplanner/emissions/EmissionsModule.java diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 3efd5dd070a..c501696f603 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -64,6 +64,7 @@ Sections follow that describe particular settings in more depth. |    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | |    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | |    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | +| [hslEmissions](#hslEmissions) | `object[]` | Configure properties for emissions file. | *Optional* | | 2.2 | | islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | |    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | |    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | @@ -767,6 +768,19 @@ This parameter sets the generic level of preference. What is the actual cost can with the `stopTransferCost` parameter in the router configuration. +

hslEmissions

+ +**Since version:** `2.2` ∙ **Type:** `object[]` ∙ **Cardinality:** `Optional` +**Path:** / + +Configure properties for emissions file. + +The emissions section of build-config.json allows you to override the default behavior of scanning +for OpenStreetMap files in the base directory. You can specify data located outside the +local filesystem (including cloud storage services) or at various different locations around +the local filesystem. + +

adaptivePruningDistance

**Since version:** `2.3` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `250` diff --git a/docs/Configuration.md b/docs/Configuration.md index 90445c57901..81233b3ad8c 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -230,6 +230,7 @@ Here is a list of all features which can be toggled on/off and their default val | `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | | `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `co2Emissions` | Enable emissions for HSL | | ✓️ | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | | `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | | `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java new file mode 100644 index 00000000000..2422cb9e01c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java @@ -0,0 +1,75 @@ +package org.opentripplanner.ext.emissions; + +public class DigitransitEmissions { + + private String db; + private String agency_id; + private String agency_name; + private String mode; + private String avg; + private int p_avg; + + public DigitransitEmissions( + String db, + String agency_id, + String agency_name, + String mode, + String avg, + int p_avg + ) { + this.db = db; + this.agency_id = agency_id; + this.agency_name = agency_name; + this.mode = mode; + this.avg = avg; + this.p_avg = p_avg; + } + + public String getDb() { + return db; + } + + public void setDb(String db) { + this.db = db; + } + + public String getAgency_id() { + return agency_id; + } + + public void setAgency_id(String agency_id) { + this.agency_id = agency_id; + } + + public String getAgency_name() { + return agency_name; + } + + public void setAgency_name(String agency_name) { + this.agency_name = agency_name; + } + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public String getAvg() { + return avg; + } + + public void setAvg(String avg) { + this.avg = avg; + } + + public int getP_avg() { + return p_avg; + } + + public void setP_avg(int p_avg) { + this.p_avg = p_avg; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java new file mode 100644 index 00000000000..345e9a8c4f3 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java @@ -0,0 +1,27 @@ +package org.opentripplanner.ext.emissions; + +import java.util.HashMap; + +//interface emissionService +// getEmissionForRoute() +//Sandbox digitransitEmissionService +public class DigitransitEmissionsService implements EmissionsService { + + private String url; + private HashMap emissionByAgency; + + public DigitransitEmissionsService(DigitransitEmissions[] emissions) { + this.url = url; + this.emissionByAgency = new HashMap<>(); + for (DigitransitEmissions e : emissions) { + this.emissionByAgency.put(e.getAgency_id(), e); + } + } + + public HashMap getEmissionByAgency() { + return emissionByAgency; + } + + @Override + public void getEmissionForRoute() {} +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java new file mode 100644 index 00000000000..9580b918463 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java @@ -0,0 +1,5 @@ +package org.opentripplanner.ext.emissions; + +public interface EmissionsService { + void getEmissionForRoute(); +} diff --git a/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java b/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java new file mode 100644 index 00000000000..a9b3152b305 --- /dev/null +++ b/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java @@ -0,0 +1,30 @@ +package org.opentripplanner.emissions; + +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.NA; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; + +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +/** + * This class is responsible for mapping OSM configuration into OSM parameters. + */ +public class EmissionsConfig { + + private String url; + private String configName; + + public EmissionsConfig(String parameterName, NodeAdapter root) { + var c = root.of(parameterName).since(V2_2).summary("Configuration for Emissions").asObject(); + + this.url = c.of("url").since(NA).summary("foo").description("bar").asString(""); + this.configName = parameterName; + } + + public String getUrl() { + return url; + } + + public String getConfigName() { + return configName; + } +} diff --git a/src/main/java/org/opentripplanner/emissions/EmissionsModule.java b/src/main/java/org/opentripplanner/emissions/EmissionsModule.java new file mode 100644 index 00000000000..c0a7c684083 --- /dev/null +++ b/src/main/java/org/opentripplanner/emissions/EmissionsModule.java @@ -0,0 +1,73 @@ +package org.opentripplanner.emissions; + +import com.google.common.io.CharStreams; +import com.google.gson.Gson; +import java.io.IOException; +import java.io.InputStreamReader; +import org.opentripplanner.ext.emissions.DigitransitEmissions; +import org.opentripplanner.ext.emissions.DigitransitEmissionsService; +import org.opentripplanner.ext.emissions.EmissionsService; +import org.opentripplanner.framework.io.HttpUtils; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.graph_builder.model.GraphBuilderModule; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.standalone.config.BuildConfig; +import org.opentripplanner.transit.service.TransitModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EmissionsModule implements GraphBuilderModule { + + private static final Logger LOG = LoggerFactory.getLogger(EmissionsModule.class); + + private TransitModel transitModel; + private Graph graph; + private DataImportIssueStore issueStore; + private BuildConfig config; + + public EmissionsModule( + TransitModel transitModel, + Graph graph, + DataImportIssueStore issueStore, + BuildConfig config + ) { + this.transitModel = transitModel; + this.graph = graph; + this.issueStore = issueStore; + this.config = config; + } + + @Override + public void buildGraph() { + LOG.info("Start emissions building!"); + String url = config.emissions.getUrl(); + if (url != null && !url.isEmpty()) { + LOG.info("Fetching data from {}", url); + try { + var data = HttpUtils.getData(url); + System.out.println("YEE"); + if (data == null) { + throw new IOException("Did not find any emissions data from url " + url); + } + + var reader = new InputStreamReader(data); + var emissionJson = CharStreams.toString(reader); + Gson gson = new Gson(); + EmissionsService emissionsService; + if (config.emissions.getConfigName().equals("digitransitEmissions")) { + DigitransitEmissions[] digitransitEmissions = gson.fromJson( + emissionJson, + DigitransitEmissions[].class + ); + emissionsService = new DigitransitEmissionsService(digitransitEmissions); + transitModel.setEmissionsService(emissionsService); + } + } catch (Exception e) { + LOG.error("ERROR " + e); + } + } + } + + @Override + public void checkInputs() {} +} diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 1d34dddb65a..1ae3b11199e 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -29,6 +29,8 @@ public enum OTPFeature { ), DebugClient(true, false, "Enable the debug web client located at the root of the web server."), FloatingBike(true, false, "Enable floating bike routing."), + + co2Emissions(false, true, "Enable emissions"), /** * If this feature flag is switched on, then the minimum transfer time is not the minimum transfer * time, but the definitive transfer time. Use this to override what we think the transfer will diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index 981fcbf75d3..c85e0267149 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -68,6 +68,7 @@ public static GraphBuilder create( boolean hasGtfs = dataSources.has(GTFS); boolean hasNetex = dataSources.has(NETEX); boolean hasTransitData = hasGtfs || hasNetex; + boolean hasEmissions = true; // TODO transitModel.initTimeZone(config.transitModelTimeZone); @@ -92,7 +93,9 @@ public static GraphBuilder create( if (hasGtfs) { graphBuilder.addModule(factory.gtfsModule()); } - + if (hasEmissions) { + graphBuilder.addModule(factory.emissionsModule()); + } if (hasNetex) { graphBuilder.addModule(factory.netexModule()); } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java index f1f6711aafd..07828eac29b 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java @@ -6,6 +6,7 @@ import java.time.ZoneId; import java.util.List; import javax.annotation.Nullable; +import org.opentripplanner.emissions.EmissionsModule; import org.opentripplanner.ext.dataoverlay.EdgeUpdaterModule; import org.opentripplanner.ext.flex.FlexLocationsToStreetEdgesMapper; import org.opentripplanner.ext.transferanalyzer.DirectTransferAnalyzer; @@ -37,6 +38,9 @@ public interface GraphBuilderFactory { GraphBuilder graphBuilder(); OsmModule osmModule(); GtfsModule gtfsModule(); + + EmissionsModule emissionsModule(); + NetexModule netexModule(); TimeZoneAdjusterModule timeZoneAdjusterModule(); TripPatternNamer tripPatternNamer(); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 9b8852977f3..2fcf28809a3 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; import org.opentripplanner.datastore.api.DataSource; +import org.opentripplanner.emissions.EmissionsModule; import org.opentripplanner.ext.dataoverlay.EdgeUpdaterModule; import org.opentripplanner.ext.dataoverlay.configure.DataOverlayFactory; import org.opentripplanner.ext.transferanalyzer.DirectTransferAnalyzer; @@ -108,6 +109,18 @@ static GtfsModule provideGtfsModule( ); } + @Provides + @Singleton + static EmissionsModule provideEmissionsModule( + GraphBuilderDataSources dataSources, + BuildConfig config, + Graph graph, + TransitModel transitModel, + DataImportIssueStore issueStore + ) { + return new EmissionsModule(transitModel, graph, issueStore, config); + } + @Provides @Singleton static NetexModule provideNetexModule( diff --git a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index aab9b0193c6..b917365c607 100644 --- a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -17,6 +17,7 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; import org.opentripplanner.datastore.api.OtpDataStoreConfig; +import org.opentripplanner.emissions.EmissionsConfig; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayConfig; import org.opentripplanner.ext.fares.FaresConfiguration; import org.opentripplanner.framework.geometry.CompactElevationProfile; @@ -166,6 +167,7 @@ public class BuildConfig implements OtpDataStoreConfig { public final Set boardingLocationTags; public final DemExtractParametersList dem; public final OsmExtractParametersList osm; + public final EmissionsConfig emissions; public final TransitFeeds transitFeeds; public boolean staticParkAndRide; public boolean staticBikeParkAndRide; @@ -625,6 +627,8 @@ that we support remote input files (cloud storage or arbitrary URLs) not all dat osm = OsmConfig.mapOsmConfig(root, "osm", osmDefaults); demDefaults = DemConfig.mapDemDefaultsConfig(root, "demDefaults"); dem = DemConfig.mapDemConfig(root, "dem", demDefaults); + emissions = new EmissionsConfig("digitransitEmissions", root); + netexDefaults = NetexConfig.mapNetexDefaultParameters(root, "netexDefaults"); gtfsDefaults = GtfsConfig.mapGtfsDefaultParameters(root, "gtfsDefaults"); transitFeeds = diff --git a/src/main/java/org/opentripplanner/transit/service/TransitModel.java b/src/main/java/org/opentripplanner/transit/service/TransitModel.java index e845aa27dfb..351324d9aa4 100644 --- a/src/main/java/org/opentripplanner/transit/service/TransitModel.java +++ b/src/main/java/org/opentripplanner/transit/service/TransitModel.java @@ -21,6 +21,7 @@ import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.flex.trip.FlexTrip; import org.opentripplanner.framework.lang.ObjectUtils; import org.opentripplanner.framework.time.ServiceDateUtils; @@ -107,6 +108,8 @@ public class TransitModel implements Serializable { private transient TransitAlertService transitAlertService; + private EmissionsService emissionsService; + @Inject public TransitModel(StopModel stopModel, Deduplicator deduplicator) { this.stopModel = Objects.requireNonNull(stopModel); @@ -576,4 +579,12 @@ private void initTimeZone() { } } } + + public EmissionsService getEmissionsService() { + return emissionsService; + } + + public void setEmissionsService(EmissionsService emissionsService) { + this.emissionsService = emissionsService; + } } diff --git a/src/test/resources/standalone/config/build-config.json b/src/test/resources/standalone/config/build-config.json index 643234fd7b7..140b0231c08 100644 --- a/src/test/resources/standalone/config/build-config.json +++ b/src/test/resources/standalone/config/build-config.json @@ -73,5 +73,8 @@ "enabled": true } } - ] + ], + "digitransitEmissions": { + "url": "foo.bar" + } } From 689d98cf5af1b1c26471f0f5495405ee886591bb Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 16 Jun 2023 11:51:04 +0300 Subject: [PATCH 02/68] remove sout --- src/main/java/org/opentripplanner/emissions/EmissionsModule.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/emissions/EmissionsModule.java b/src/main/java/org/opentripplanner/emissions/EmissionsModule.java index c0a7c684083..eb8fab780ba 100644 --- a/src/main/java/org/opentripplanner/emissions/EmissionsModule.java +++ b/src/main/java/org/opentripplanner/emissions/EmissionsModule.java @@ -45,7 +45,6 @@ public void buildGraph() { LOG.info("Fetching data from {}", url); try { var data = HttpUtils.getData(url); - System.out.println("YEE"); if (data == null) { throw new IOException("Did not find any emissions data from url " + url); } From 46c479b189524530dd473d1cddcf077dc710beb7 Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 5 Jul 2023 17:24:16 +0300 Subject: [PATCH 03/68] calculate co2 emissions --- .../ext/emissions/DigitransitEmissions.java | 6 +- .../emissions/DigitransitEmissionsAgency.java | 79 +++++++++++++++ .../emissions/DigitransitEmissionsMode.java | 55 +++++++++++ .../DigitransitEmissionsService.java | 99 +++++++++++++++++-- .../ext/emissions/EmissionsFilter.java | 27 +++++ .../ext/emissions/EmissionsService.java | 12 ++- .../legacygraphqlapi/schema.graphqls | 3 + .../api/mapping/ItineraryMapper.java | 1 + .../api/model/ApiItinerary.java | 4 + .../emissions/EmissionsModule.java | 1 + .../graph_builder/GraphBuilder.java | 9 +- .../opentripplanner/model/plan/Itinerary.java | 14 +++ .../org/opentripplanner/model/plan/Leg.java | 5 + .../ItineraryListFilterChainBuilder.java | 14 +++ .../RouteRequestToFilterChainMapper.java | 1 + .../opentripplanner/routing/graph/Graph.java | 10 ++ 16 files changed, 325 insertions(+), 15 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java index 2422cb9e01c..38c9c522906 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java @@ -1,6 +1,10 @@ package org.opentripplanner.ext.emissions; -public class DigitransitEmissions { +import java.io.Serializable; +import org.opentripplanner.framework.lang.Sandbox; + +@Sandbox +public class DigitransitEmissions implements Serializable { private String db; private String agency_id; diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java new file mode 100644 index 00000000000..547e63c1f8d --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java @@ -0,0 +1,79 @@ +package org.opentripplanner.ext.emissions; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.transit.model.basic.TransitMode; + +@Sandbox +public class DigitransitEmissionsAgency implements Serializable { + + private String db; + private String agency_id; + private String agency_name; + private Map modes; + + public DigitransitEmissionsAgency(String db, String agency_id, String agency_name) { + this.db = db; + this.agency_id = agency_id; + this.agency_name = agency_name; + this.modes = new HashMap<>(); + } + + public String getDb() { + return db; + } + + public String getAgency_id() { + return agency_id; + } + + public String getAgency_name() { + return agency_name; + } + + public Map getModes() { + return modes; + } + + public void setModes(Map modes) { + this.modes = modes; + } + + public void addMode(DigitransitEmissionsMode mode) { + this.modes.put(mode.getName(), mode); + } + + public DigitransitEmissionsMode getMode(TransitMode mode) { + if (this.modes.containsKey(mode.name())) { + return this.modes.get(mode.name()); + } + return null; + } + + /** + * Returns the average CO2 emissions (g/km) per person for a specific transit mode. + * @param modeName name of transit mode + * @return CO2 emissions (g/km) per person + */ + public float getAverageCo2EmissionsByModePerPerson(String modeName) { + if (this.modes.containsKey(modeName)) { + return this.modes.get(modeName).getAverageCo2EmissionsPerPersonPerKm(); + } + return 0; + } + + /** + * Returns the CO2 emissions (g/km) per person for a specific transit mode and the number of + * passengers. + * @param modeName name of transit mode + * @return CO2 emissions (g/km) per person + */ + public float getCo2EmissionsByModeAndNumberOfPassengers(String modeName, int numberOfPassengers) { + if (this.modes.containsKey(modeName)) { + return this.modes.get(modeName).getEmissionsPerPersonByNumberOfPassengers(numberOfPassengers); + } + return 0; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java new file mode 100644 index 00000000000..9c69cc67dad --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java @@ -0,0 +1,55 @@ +package org.opentripplanner.ext.emissions; + +import java.io.Serializable; +import org.opentripplanner.framework.lang.Sandbox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Sandbox +public class DigitransitEmissionsMode implements Serializable { + + private String name; + private float avg; + private int p_avg; + private float averageCo2EmissionsPerPersonPerKm; + + private static final Logger LOG = LoggerFactory.getLogger(DigitransitEmissionsMode.class); + + /** + * @param mode transit mode name + * @param avg average CO2 emissions in grams per kilometre + * @param p_avg average number of passengers per vehicle + */ + public DigitransitEmissionsMode(String mode, String avg, int p_avg) { + this.name = mode; + this.p_avg = p_avg; + + try { + this.avg = Float.parseFloat(avg); + } catch (NumberFormatException e) { + LOG.warn("Converting Digitransit emissions average value failed.", e); + this.avg = 0.0F; + } + this.averageCo2EmissionsPerPersonPerKm = getEmissionsPerPersonByNumberOfPassengers(p_avg); + } + + public String getName() { + return name; + } + + public double getAvg() { + return avg; + } + + public int getP_avg() { + return p_avg; + } + + public float getAverageCo2EmissionsPerPersonPerKm() { + return this.averageCo2EmissionsPerPersonPerKm; + } + + public float getEmissionsPerPersonByNumberOfPassengers(int numberOfPassengers) { + return Math.round(this.avg / numberOfPassengers); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java index 345e9a8c4f3..a792f669bb1 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java @@ -1,27 +1,110 @@ package org.opentripplanner.ext.emissions; import java.util.HashMap; +import java.util.List; +import org.opentripplanner.ext.flex.FlexibleTransitLeg; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.ScheduledTransitLeg; +import org.opentripplanner.model.plan.StreetLeg; +import org.opentripplanner.model.plan.TransitLeg; +import org.opentripplanner.street.search.TraverseMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; -//interface emissionService -// getEmissionForRoute() -//Sandbox digitransitEmissionService +@Sandbox public class DigitransitEmissionsService implements EmissionsService { private String url; - private HashMap emissionByAgency; + private HashMap emissionByAgency; public DigitransitEmissionsService(DigitransitEmissions[] emissions) { this.url = url; this.emissionByAgency = new HashMap<>(); for (DigitransitEmissions e : emissions) { - this.emissionByAgency.put(e.getAgency_id(), e); + DigitransitEmissionsMode mode = new DigitransitEmissionsMode( + e.getMode(), + e.getAvg(), + e.getP_avg() + ); + if (!this.emissionByAgency.containsKey(e.getAgency_id())) { + this.emissionByAgency.put( + e.getAgency_id(), + new DigitransitEmissionsAgency(e.getDb(), e.getAgency_id(), e.getAgency_name()) + ); + } + this.emissionByAgency.get(e.getAgency_id()).addMode(mode); } } - public HashMap getEmissionByAgency() { + @Override + public HashMap getEmissionByAgency() { return emissionByAgency; } - @Override - public void getEmissionForRoute() {} + public DigitransitEmissionsAgency getEmissionsByAgencyId(String agencyId) { + if (agencyId != null && this.emissionByAgency.containsKey(agencyId)) { + return this.emissionByAgency.get(agencyId); + } + return null; + } + + public float getEmissionsForRoute(Itinerary itinerary) { + List transitLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg) + .map(TransitLeg.class::cast) + .toList(); + + if (!transitLegs.isEmpty()) { + return getEmissionsForTransitRoute(transitLegs); + } + + List carLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof StreetLeg) + .map(StreetLeg.class::cast) + .filter(leg -> leg.getMode() == TraverseMode.CAR) + .toList(); + + if (!carLegs.isEmpty()) { + return getEmissionsForCarRoute(carLegs); + } + + return 0.0F; + } + + private float getEmissionsForTransitRoute(List transitLegs) { + return (float) transitLegs + .stream() + .mapToDouble(leg -> { + FeedScopedId agencyIdFeedScoped = leg.getAgency().getId(); + double legDistanceInKm = leg.getDistanceMeters() / 1000; + DigitransitEmissionsAgency digitransitEmissionsAgency = getEmissionsByAgencyId( + agencyIdFeedScoped.getId() + ); + float emissionsForAgencyPerKm = digitransitEmissionsAgency != null + ? digitransitEmissionsAgency.getAverageCo2EmissionsByModePerPerson(leg.getMode().name()) + : 0; + double emissionsPerLeg = legDistanceInKm * emissionsForAgencyPerKm; + return emissionsPerLeg; + }) + .sum(); + } + + public float getEmissionsForCarRoute(List carLegs) { + return (float) carLegs + .stream() + .mapToDouble(leg -> { + DigitransitEmissionsAgency digitransitEmissionsAgency = getEmissionsByAgencyId("CAR"); + float avgCarEmissions = digitransitEmissionsAgency.getAverageCo2EmissionsByModePerPerson( + leg.getMode().name() + ); + double carLegDistanceInKm = leg.getDistanceMeters() / 1000; + double carEmissionsPerLeg = carLegDistanceInKm * avgCarEmissions; + return carEmissionsPerLeg; + }) + .sum(); + } } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java new file mode 100644 index 00000000000..934632ff129 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java @@ -0,0 +1,27 @@ +package org.opentripplanner.ext.emissions; + +import java.util.List; +import java.util.Objects; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Sandbox +public record EmissionsFilter(EmissionsService emissionsService) implements ItineraryListFilter { + private static final Logger LOG = LoggerFactory.getLogger(EmissionsFilter.class); + + @Override + public List filter(List itineraries) { + return itineraries + .stream() + .peek(i -> { + float emissions = emissionsService.getEmissionsForRoute(i); + if (Objects.nonNull(emissions)) { + i.setEmissions(emissions); + } + }) + .toList(); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java index 9580b918463..a0a824db858 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java @@ -1,5 +1,13 @@ package org.opentripplanner.ext.emissions; -public interface EmissionsService { - void getEmissionForRoute(); +import java.io.Serializable; +import java.util.HashMap; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.model.plan.Itinerary; + +@Sandbox +public interface EmissionsService extends Serializable { + HashMap getEmissionByAgency(); + + float getEmissionsForRoute(Itinerary itinerary); } diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index dd6bf025265..ae1519a5adb 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1374,6 +1374,9 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float + """CO2 emissions of the trip on this itinerary, in grams per person.""" + emissions: Float + """ A list of Legs. Each Leg is either a walking (cycling, car) portion of the itinerary, or a transit leg on a particular vehicle. So a itinerary where the diff --git a/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java b/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java index e65700b2f08..eca57e0d8d2 100644 --- a/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java @@ -45,6 +45,7 @@ public ApiItinerary mapItinerary(Itinerary domain) { api.tooSloped = domain.isTooSloped(); api.arrivedAtDestinationWithRentedBicycle = domain.isArrivedAtDestinationWithRentedVehicle(); api.fare = fareMapper.mapFare(domain); + api.emissions = domain.getEmissions(); api.legs = legMapper.mapLegs(domain.getLegs()); api.systemNotices = SystemNoticeMapper.mapSystemNotices(domain.getSystemNotices()); api.accessibilityScore = domain.getAccessibilityScore(); diff --git a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java index e2d205de13e..40e41e56d64 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java +++ b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java @@ -80,6 +80,10 @@ public class ApiItinerary { */ public ApiItineraryFares fare = new ApiItineraryFares(Map.of(), Map.of(), null, null); + /** + * CO2 emissions of this trip + */ + public Float emissions; /** * A list of Legs. Each Leg is either a walking (cycling, car) portion of the trip, or a transit * trip on a particular vehicle. So a trip where the use walks to the Q train, transfers to the 6, diff --git a/src/main/java/org/opentripplanner/emissions/EmissionsModule.java b/src/main/java/org/opentripplanner/emissions/EmissionsModule.java index eb8fab780ba..0c80fe00b62 100644 --- a/src/main/java/org/opentripplanner/emissions/EmissionsModule.java +++ b/src/main/java/org/opentripplanner/emissions/EmissionsModule.java @@ -60,6 +60,7 @@ public void buildGraph() { ); emissionsService = new DigitransitEmissionsService(digitransitEmissions); transitModel.setEmissionsService(emissionsService); + graph.setEmissionsService(new DigitransitEmissionsService(digitransitEmissions)); } } catch (Exception e) { LOG.error("ERROR " + e); diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index c85e0267149..382b03d096f 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -68,7 +68,6 @@ public static GraphBuilder create( boolean hasGtfs = dataSources.has(GTFS); boolean hasNetex = dataSources.has(NETEX); boolean hasTransitData = hasGtfs || hasNetex; - boolean hasEmissions = true; // TODO transitModel.initTimeZone(config.transitModelTimeZone); @@ -93,9 +92,7 @@ public static GraphBuilder create( if (hasGtfs) { graphBuilder.addModule(factory.gtfsModule()); } - if (hasEmissions) { - graphBuilder.addModule(factory.emissionsModule()); - } + if (hasNetex) { graphBuilder.addModule(factory.netexModule()); } @@ -159,6 +156,10 @@ public static GraphBuilder create( graphBuilder.addModuleOptional(factory.dataOverlayFactory()); } + if (OTPFeature.co2Emissions.isOn()) { + graphBuilder.addModule(factory.emissionsModule()); + } + graphBuilder.addModule(factory.calculateWorldEnvelopeModule()); return graphBuilder; diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index 1fb8b80f597..15d25350c5b 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -47,6 +47,8 @@ public class Itinerary { /* Sandbox experimental properties */ private Float accessibilityScore; + private float emissions; + /* other properties */ private final List systemNotices = new ArrayList<>(); @@ -245,6 +247,7 @@ public String toString() { .addNum("elevationGained", elevationGained, 0.0) .addCol("legs", legs) .addObj("fare", fare) + .addObj("emissions", emissions) .toString(); } @@ -563,4 +566,15 @@ public List getScheduledTransitLegs() { .map(ScheduledTransitLeg.class::cast) .toList(); } + + /** + * The co2 emissions of this itinerary. + */ + public void setEmissions(float emissions) { + this.emissions = emissions; + } + + public float getEmissions() { + return this.emissions; + } } diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java index 1b6620981e6..9c9060d2528 100644 --- a/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -418,6 +418,11 @@ default Float accessibilityScore() { return null; } + @Nullable + default Float emissions() { + return null; + } + default Boolean getRentedVehicle() { return null; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index 3597558978b..70ec55f6bee 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -9,6 +9,8 @@ import java.util.function.Consumer; import java.util.function.Function; import org.opentripplanner.ext.accessibilityscore.AccessibilityScoreFilter; +import org.opentripplanner.ext.emissions.EmissionsFilter; +import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.fares.FaresFilter; import org.opentripplanner.framework.lang.Sandbox; import org.opentripplanner.model.plan.Itinerary; @@ -73,6 +75,9 @@ public class ItineraryListFilterChainBuilder { private boolean removeItinerariesWithSameRoutesAndStops; private double minBikeParkingDistance; + @Sandbox + private EmissionsService emissionsService; + @Sandbox private ItineraryListFilter rideHailingFilter; @@ -269,6 +274,11 @@ public ItineraryListFilterChainBuilder withFares(FareService fareService) { return this; } + public ItineraryListFilterChainBuilder withEmissions(EmissionsService emissionsService) { + this.emissionsService = emissionsService; + return this; + } + public ItineraryListFilterChainBuilder withMinBikeParkingDistance(double distance) { this.minBikeParkingDistance = distance; return this; @@ -315,6 +325,10 @@ public ItineraryListFilterChain build() { filters.add(new FaresFilter(faresService)); } + if (emissionsService != null) { + filters.add(new EmissionsFilter(emissionsService)); + } + if (transitAlertService != null) { filters.add(new TransitAlertFilter(transitAlertService, getMultiModalStation)); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 7f5c72c261c..857ba40d68d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -66,6 +66,7 @@ public static ItineraryListFilterChain createFilterChain( request.preferences().wheelchair().maxSlope() ) .withFares(context.graph().getFareService()) + .withEmissions(context.graph().getEmissionsService()) .withMinBikeParkingDistance(minBikeParkingDistance(request)) .withRemoveTimeshiftedItinerariesWithSameRoutesAndStops( params.removeItinerariesWithSameRoutesAndStops() diff --git a/src/main/java/org/opentripplanner/routing/graph/Graph.java b/src/main/java/org/opentripplanner/routing/graph/Graph.java index 2b55662d7ca..2c2be292bef 100644 --- a/src/main/java/org/opentripplanner/routing/graph/Graph.java +++ b/src/main/java/org/opentripplanner/routing/graph/Graph.java @@ -17,6 +17,7 @@ import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings; +import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.framework.geometry.CompactElevationProfile; import org.opentripplanner.framework.geometry.GeometryUtils; @@ -113,6 +114,7 @@ public class Graph implements Serializable { */ public DataOverlayParameterBindings dataOverlayParameterBindings; private LuceneIndex luceneIndex; + private EmissionsService emissionsService; @Inject public Graph( @@ -357,6 +359,14 @@ public void setFareService(FareService fareService) { this.fareService = fareService; } + public EmissionsService getEmissionsService() { + return emissionsService; + } + + public void setEmissionsService(EmissionsService emissionsService) { + this.emissionsService = emissionsService; + } + public LuceneIndex getLuceneIndex() { return luceneIndex; } From b7cd1385f9fabecae01abd70b76db57c28f99831 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 21 Jul 2023 09:28:51 +0300 Subject: [PATCH 04/68] updated configuration from placeholder values. Adjusted emissions module data fetching since the utility method was removed. Also initial unit tests added --- docs/BuildConfiguration.md | 13 +- .../DigitransitEmissionsAgencyTest.java | 40 ++++++ .../DigitransitEmissionsModeTest.java | 20 +++ .../DigitransitEmissionsServiceTest.java | 118 ++++++++++++++++++ .../DigitransitEmissionsService.java | 8 +- .../emissions/EmissionsConfig.java | 7 +- .../emissions/EmissionsModule.java | 14 +-- 7 files changed, 198 insertions(+), 22 deletions(-) create mode 100644 src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java create mode 100644 src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java create mode 100644 src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 62f1352b941..7981cd93f7f 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -64,7 +64,7 @@ Sections follow that describe particular settings in more depth. |    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | |    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | |    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | -| [hslEmissions](#hslEmissions) | `object[]` | Configure properties for emissions file. | *Optional* | | 2.2 | +| [DigitransitEmissions](#digitransitEmissions) | `object[]` | Configure properties for emissions file. | *Optional* | | 2.2 | | islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | |    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | |    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | @@ -768,17 +768,16 @@ This parameter sets the generic level of preference. What is the actual cost can with the `stopTransferCost` parameter in the router configuration. -

hslEmissions

+

DigitransitEmissions

-**Since version:** `2.2` ∙ **Type:** `object[]` ∙ **Cardinality:** `Optional` +**Since version:** `2.4` ∙ **Type:** `object[]` ∙ **Cardinality:** `Optional` **Path:** / Configure properties for emissions file. -The emissions section of build-config.json allows you to override the default behavior of scanning -for OpenStreetMap files in the base directory. You can specify data located outside the -local filesystem (including cloud storage services) or at various different locations around -the local filesystem. +By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) +emissions associated with various transportation modes and routes. This data is then used to perform +emission calculations to different transport modes and car.

adaptivePruningDistance

diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java new file mode 100644 index 00000000000..9a294a418f6 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java @@ -0,0 +1,40 @@ +package org.opentripplanner.ext.emissions; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model.basic.TransitMode; + +class DigitransitEmissionsAgencyTest { + + private DigitransitEmissionsAgency agency; + + @BeforeEach + protected void setUp() throws Exception { + DigitransitEmissionsMode mode = new DigitransitEmissionsMode("BUS", "120", 12); + this.agency = new DigitransitEmissionsAgency("db", "FOO", "FOO_COMP"); + + this.agency.addMode(mode); + } + + @Test + void testGetModeDefaultBehavior() { + assertEquals("BUS", agency.getMode(TransitMode.BUS).getName()); + } + + @Test + void testGetModNotNull() { + assertNull(agency.getMode(TransitMode.AIRPLANE)); + } + + @Test + void getAverageCo2EmissionsByModePerPerson() { + assertEquals(10, this.agency.getAverageCo2EmissionsByModePerPerson("BUS")); + } + + @Test + void getAverageCo2EmissionsByModePerPersonNullMode() { + assertEquals(0, this.agency.getAverageCo2EmissionsByModePerPerson("AIRPLANE")); + } +} diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java new file mode 100644 index 00000000000..a101e2e3782 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java @@ -0,0 +1,20 @@ +package org.opentripplanner.ext.emissions; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class DigitransitEmissionsModeTest { + + @Test + void EmissionsModeWithNormalAvg() { + DigitransitEmissionsMode mode = new DigitransitEmissionsMode("BUS", "120", 12); + assertEquals(120.0, mode.getAvg()); + } + + @Test + void EmissionsModeWithIncorrectAvg() { + DigitransitEmissionsMode mode = new DigitransitEmissionsMode("BUS", "Foo", 12); + assertEquals(0, mode.getAvg()); + } +} diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java new file mode 100644 index 00000000000..693f12d04d8 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java @@ -0,0 +1,118 @@ +package org.opentripplanner.ext.emissions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; + +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.model.StopTime; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.model.plan.ScheduledTransitLeg; +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.timetable.Trip; +import org.opentripplanner.transit.model.timetable.TripTimes; + +class DigitransitEmissionsServiceTest { + + private DigitransitEmissionsService eservice; + + static final ZonedDateTime TIME = OffsetDateTime + .parse("2023-07-20T17:49:06+03:00") + .toZonedDateTime(); + + private static final Agency subject = Agency + .of(TransitModelForTest.id("1")) + .withName("Foo_CO") + .withTimezone("Europe/Helsinki") + .build(); + + @BeforeEach + void SetUp() { + DigitransitEmissions e = new DigitransitEmissions("db", "F:1", "Foo_CO", "BUS", "120", 12); + DigitransitEmissions[] list = new DigitransitEmissions[2]; + list[0] = e; + list[1] = new DigitransitEmissions("db", "CAR", "CAR", "CAR", "1100", 1); + this.eservice = new DigitransitEmissionsService(list); + } + + @Test + void getEmissionsByAgencyId() { + assertEquals("Foo_CO", eservice.getEmissionsByAgencyId("FOO").getAgency_name()); + } + + @Test + void getEmissionsForRoute() { + var route = TransitModelForTest.route(id("2")).withAgency(subject).build(); + List legs = new ArrayList<>(); + var pattern = TransitModelForTest + .tripPattern("1", route) + .withStopPattern(TransitModelForTest.stopPattern(3)) + .build(); + var stoptime = new StopTime(); + var stoptimes = new ArrayList(); + stoptimes.add(stoptime); + var trip = Trip + .of(FeedScopedId.parseId("FOO:BAR")) + .withMode(TransitMode.BUS) + .withRoute(route) + .build(); + var leg = new ScheduledTransitLeg( + new TripTimes(trip, stoptimes, new Deduplicator()), + pattern, + 0, + 2, + TIME, + TIME.plusMinutes(10), + TIME.toLocalDate(), + ZoneIds.BERLIN, + null, + null, + 100, + null + ); + legs.add(leg); + Itinerary i = new Itinerary(legs); + assertEquals(0, eservice.getEmissionsForRoute(i)); + } + + @Test + void getEmissionsForCarRoute() { + var route = TransitModelForTest.route(id("2")).withAgency(subject).build(); + List legs = new ArrayList<>(); + var pattern = TransitModelForTest + .tripPattern("1", route) + .withStopPattern(TransitModelForTest.stopPattern(3)) + .build(); + var stoptime = new StopTime(); + var stoptimes = new ArrayList(); + stoptimes.add(stoptime); + var trip = Trip + .of(FeedScopedId.parseId("F:A")) + .withMode(TransitMode.BUS) + .withRoute(route) + .build(); + var leg = StreetLeg + .create() + .withMode(TraverseMode.CAR) + .withDistanceMeters(214.4) + .withStartTime(TIME) + .withEndTime(TIME.plus(1, ChronoUnit.HOURS)) + .build(); + legs.add(leg); + Itinerary i = new Itinerary(legs); + assertEquals(235.83999633789062, eservice.getEmissionsForRoute(i)); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java index a792f669bb1..1aa54e23dd4 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java @@ -14,11 +14,9 @@ @Sandbox public class DigitransitEmissionsService implements EmissionsService { - private String url; private HashMap emissionByAgency; public DigitransitEmissionsService(DigitransitEmissions[] emissions) { - this.url = url; this.emissionByAgency = new HashMap<>(); for (DigitransitEmissions e : emissions) { DigitransitEmissionsMode mode = new DigitransitEmissionsMode( @@ -93,11 +91,13 @@ private float getEmissionsForTransitRoute(List transitLegs) { .sum(); } - public float getEmissionsForCarRoute(List carLegs) { + private float getEmissionsForCarRoute(List carLegs) { return (float) carLegs .stream() .mapToDouble(leg -> { - DigitransitEmissionsAgency digitransitEmissionsAgency = getEmissionsByAgencyId("CAR"); + DigitransitEmissionsAgency digitransitEmissionsAgency = getEmissionsByAgencyId( + TraverseMode.CAR.toString() + ); float avgCarEmissions = digitransitEmissionsAgency.getAverageCo2EmissionsByModePerPerson( leg.getMode().name() ); diff --git a/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java b/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java index a9b3152b305..82a16174e21 100644 --- a/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java +++ b/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java @@ -1,7 +1,6 @@ package org.opentripplanner.emissions; -import static org.opentripplanner.standalone.config.framework.json.OtpVersion.NA; -import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_4; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -14,9 +13,9 @@ public class EmissionsConfig { private String configName; public EmissionsConfig(String parameterName, NodeAdapter root) { - var c = root.of(parameterName).since(V2_2).summary("Configuration for Emissions").asObject(); + var c = root.of(parameterName).since(V2_4).summary("Configuration for Emissions").asObject(); - this.url = c.of("url").since(NA).summary("foo").description("bar").asString(""); + this.url = c.of("url").since(V2_4).description("Url to emissions json file").asString(""); this.configName = parameterName; } diff --git a/src/main/java/org/opentripplanner/emissions/EmissionsModule.java b/src/main/java/org/opentripplanner/emissions/EmissionsModule.java index 0c80fe00b62..67b81bdaf27 100644 --- a/src/main/java/org/opentripplanner/emissions/EmissionsModule.java +++ b/src/main/java/org/opentripplanner/emissions/EmissionsModule.java @@ -1,13 +1,14 @@ package org.opentripplanner.emissions; -import com.google.common.io.CharStreams; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import java.io.IOException; -import java.io.InputStreamReader; +import java.net.URI; +import java.util.Map; import org.opentripplanner.ext.emissions.DigitransitEmissions; import org.opentripplanner.ext.emissions.DigitransitEmissionsService; import org.opentripplanner.ext.emissions.EmissionsService; -import org.opentripplanner.framework.io.HttpUtils; +import org.opentripplanner.framework.io.OtpHttpClient; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.model.GraphBuilderModule; import org.opentripplanner.routing.graph.Graph; @@ -44,18 +45,17 @@ public void buildGraph() { if (url != null && !url.isEmpty()) { LOG.info("Fetching data from {}", url); try { - var data = HttpUtils.getData(url); + var http = new OtpHttpClient(); + var data = http.getAndMapAsJsonNode(new URI(url), Map.of(), new ObjectMapper()); if (data == null) { throw new IOException("Did not find any emissions data from url " + url); } - var reader = new InputStreamReader(data); - var emissionJson = CharStreams.toString(reader); Gson gson = new Gson(); EmissionsService emissionsService; if (config.emissions.getConfigName().equals("digitransitEmissions")) { DigitransitEmissions[] digitransitEmissions = gson.fromJson( - emissionJson, + String.valueOf(data), DigitransitEmissions[].class ); emissionsService = new DigitransitEmissionsService(digitransitEmissions); From 06f7f79d804cd4db77548b613eed0f7885064929 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 21 Jul 2023 14:09:40 +0300 Subject: [PATCH 05/68] initial documentation --- docs/sandbox/DigitransitEmissions.md | 69 ++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 docs/sandbox/DigitransitEmissions.md diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md new file mode 100644 index 00000000000..1d1d0e20d80 --- /dev/null +++ b/docs/sandbox/DigitransitEmissions.md @@ -0,0 +1,69 @@ +# Digitransit CO2 Emissions calcuation + +## Contact Info + +- Digitransit Team + +## Documentation + +CO2 Emissions and ability to show them via plan queries in itinerary by Digitransit team. +This implementation enables OTP to retrieve CO2 information, which is then utilized during +itinerary queries. The emissions are represented in grams per kilometer (g/Km) unit + +Emissions data should be in json array with objects that have the following properties: + +`db`: database it refers to or CAR when referring to personal travel by car. + +`agency_id`: agency id as appeared in GTFS or CAR when referring to personal travel by car. + +`agency_name`: Name of agency, or CAR when referring to personal travel by a car. + +`mode`: mode of transportation it provides or CAR when referring to personal travel by car. + +`avg`: CO2 -eq value for that agency at grams/Km units + +`p_avg`: Average passenger count for that mode of travel provided by that agency. + +For example: +```json +[ +{"db": "FOO", "agency_id": "FOO", "agency_name": "FOO_CO", "mode": "BUS", "avg": "88.17", "p_avg": 32}, +{"db": "CAR", "agency_id": "CAR", "agency_name": "CAR", "mode": "CAR", "avg": "123.7", "p_avg": 1} +] +``` + +Emissions data is loaded from the provided source and embedded in graph building process +as part of the itinerary. + + +### Configuration +To enable this functionality, you need to add the "co2Emissions" feature in the +`otp-config.json` file. include the `digitransitEmissions` object in the +`build-config.json` file. The `digitransitEmissions` object should contain a parameter called `url`, +which should be set to the location where the emissions data is stored. + +```json +//otp-config.json +{ + "co2Emissions": true +} +``` +include the `digitransitEmissions` object in the +`build-config.json` file. Object should contain `url` parameter, +which should be set to the location where the emissions data is stored. + +```json +//build-config.json +{ + "digitransitEmissions": { + "url": "https://your-url" + } +} +``` +## Changelog + +### OTP 2.4 + +- Initial implementation of Emissions calculation + + From 2df7d32d019e5e33242f670d6339af92382a2c99 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 21 Jul 2023 14:22:09 +0300 Subject: [PATCH 06/68] improved docs --- docs/Configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index e1b8275ad94..f032474cf01 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -231,7 +231,7 @@ Here is a list of all features which can be toggled on/off and their default val | `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | | `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | ✓️ | -| `co2Emissions` | Enable emissions for HSL | | ✓️ | +| `co2Emissions` | Enable emissions calculation and data handling. | | ✓️ | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | | `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | | `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | From 72c03b14966320e0b8517d73595e050f1263b064 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Tue, 25 Jul 2023 13:31:24 +0300 Subject: [PATCH 07/68] Fixed typo --- docs/sandbox/DigitransitEmissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md index 1d1d0e20d80..3fb7b759eb2 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/DigitransitEmissions.md @@ -1,4 +1,4 @@ -# Digitransit CO2 Emissions calcuation +# Digitransit CO2 Emissions calculation ## Contact Info From 9af1ba50c7927356115e1e58e961676d082601c8 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Tue, 1 Aug 2023 09:37:50 +0300 Subject: [PATCH 08/68] Fixed typos from tests and docs --- docs/BuildConfiguration.md | 2 +- docs/sandbox/DigitransitEmissions.md | 6 +++--- .../ext/emissions/DigitransitEmissionsAgencyTest.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 7981cd93f7f..30c3520f5cf 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -776,7 +776,7 @@ with the `stopTransferCost` parameter in the router configuration. Configure properties for emissions file. By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) -emissions associated with various transportation modes and routes. This data is then used to perform +emissions associated with various transportation modes and routes. This data is then used for perform emission calculations to different transport modes and car. diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md index 3fb7b759eb2..e8a33addd59 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/DigitransitEmissions.md @@ -12,7 +12,7 @@ itinerary queries. The emissions are represented in grams per kilometer (g/Km) u Emissions data should be in json array with objects that have the following properties: -`db`: database it refers to or CAR when referring to personal travel by car. +`db`: database it refers to or CAR when referring to personal travel by car. For Example Linkki, HSL `agency_id`: agency id as appeared in GTFS or CAR when referring to personal travel by car. @@ -20,7 +20,7 @@ Emissions data should be in json array with objects that have the following prop `mode`: mode of transportation it provides or CAR when referring to personal travel by car. -`avg`: CO2 -eq value for that agency at grams/Km units +`avg`: Carbon dioxide equivalent value for the `mode` at grams/Km units `p_avg`: Average passenger count for that mode of travel provided by that agency. @@ -38,7 +38,7 @@ as part of the itinerary. ### Configuration To enable this functionality, you need to add the "co2Emissions" feature in the -`otp-config.json` file. include the `digitransitEmissions` object in the +`otp-config.json` file. Include the `digitransitEmissions` object in the `build-config.json` file. The `digitransitEmissions` object should contain a parameter called `url`, which should be set to the location where the emissions data is stored. diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java index 9a294a418f6..72780aa3b06 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java @@ -20,12 +20,12 @@ protected void setUp() throws Exception { @Test void testGetModeDefaultBehavior() { - assertEquals("BUS", agency.getMode(TransitMode.BUS).getName()); + assertEquals("BUS", this.agency.getMode(TransitMode.BUS).getName()); } @Test - void testGetModNotNull() { - assertNull(agency.getMode(TransitMode.AIRPLANE)); + void testGetModeNull() { + assertNull(this.agency.getMode(TransitMode.AIRPLANE)); } @Test From a075ffab57f8c88579a44fdec4b9fa9bdbe026a2 Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 3 Aug 2023 16:16:26 +0300 Subject: [PATCH 09/68] Emissions logic refined, more tests + fixes --- .../DigitransitEmissionsAgencyTest.java | 18 ++++++-- .../DigitransitEmissionsModeTest.java | 2 +- .../DigitransitEmissionsServiceTest.java | 6 +-- .../emissions/DigitransitEmissionsAgency.java | 26 +++++++---- .../emissions/DigitransitEmissionsMode.java | 4 +- .../DigitransitEmissionsService.java | 43 +++++++++---------- .../ext/emissions/EmissionsFilter.java | 2 +- .../ext/emissions/EmissionsService.java | 2 +- 8 files changed, 61 insertions(+), 42 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java index 72780aa3b06..7c8e519b5de 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java @@ -29,12 +29,22 @@ void testGetModeNull() { } @Test - void getAverageCo2EmissionsByModePerPerson() { - assertEquals(10, this.agency.getAverageCo2EmissionsByModePerPerson("BUS")); + void getAverageCo2EmissionsByModeAndDistancePerPerson() { + assertEquals(5, this.agency.getAverageCo2EmissionsByModeAndDistancePerPerson("BUS", 0.5)); } @Test - void getAverageCo2EmissionsByModePerPersonNullMode() { - assertEquals(0, this.agency.getAverageCo2EmissionsByModePerPerson("AIRPLANE")); + void getAverageCo2EmissionsByModeAndDistancePerPersonNullMode() { + assertEquals(-1, this.agency.getAverageCo2EmissionsByModeAndDistancePerPerson("AIRPLANE", 1)); + } + + @Test + void getAverageCo2EmissionsByModeAndDistancePerPersonZeroDistance() { + assertEquals(0, this.agency.getAverageCo2EmissionsByModeAndDistancePerPerson("BUS", 0)); + } + + @Test + void getAverageCo2EmissionsByModeAndDistancePerPersonNegativeDistance() { + assertEquals(-1, this.agency.getAverageCo2EmissionsByModeAndDistancePerPerson("BUS", -1)); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java index a101e2e3782..6fb6d779144 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java @@ -15,6 +15,6 @@ void EmissionsModeWithNormalAvg() { @Test void EmissionsModeWithIncorrectAvg() { DigitransitEmissionsMode mode = new DigitransitEmissionsMode("BUS", "Foo", 12); - assertEquals(0, mode.getAvg()); + assertEquals(-1, mode.getAvg()); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java index 693f12d04d8..a42b81115ec 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java @@ -34,7 +34,7 @@ class DigitransitEmissionsServiceTest { .toZonedDateTime(); private static final Agency subject = Agency - .of(TransitModelForTest.id("1")) + .of(TransitModelForTest.id("F:1")) .withName("Foo_CO") .withTimezone("Europe/Helsinki") .build(); @@ -50,7 +50,7 @@ void SetUp() { @Test void getEmissionsByAgencyId() { - assertEquals("Foo_CO", eservice.getEmissionsByAgencyId("FOO").getAgency_name()); + assertEquals("Foo_CO", eservice.getEmissionsByAgencyId("F:1").getAgency_name()); } @Test @@ -113,6 +113,6 @@ void getEmissionsForCarRoute() { .build(); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(235.83999633789062, eservice.getEmissionsForRoute(i)); + assertEquals(235.83999633789062F, eservice.getEmissionsForRoute(i)); } } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java index 547e63c1f8d..a5b23d10844 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java @@ -57,11 +57,14 @@ public DigitransitEmissionsMode getMode(TransitMode mode) { * @param modeName name of transit mode * @return CO2 emissions (g/km) per person */ - public float getAverageCo2EmissionsByModePerPerson(String modeName) { - if (this.modes.containsKey(modeName)) { - return this.modes.get(modeName).getAverageCo2EmissionsPerPersonPerKm(); + public double getAverageCo2EmissionsByModeAndDistancePerPerson( + String modeName, + double distanceInKm + ) { + if (this.modes.containsKey(modeName) && distanceInKm >= 0) { + return this.modes.get(modeName).getAverageCo2EmissionsPerPersonPerKm() * distanceInKm; } - return 0; + return -1; } /** @@ -70,10 +73,17 @@ public float getAverageCo2EmissionsByModePerPerson(String modeName) { * @param modeName name of transit mode * @return CO2 emissions (g/km) per person */ - public float getCo2EmissionsByModeAndNumberOfPassengers(String modeName, int numberOfPassengers) { - if (this.modes.containsKey(modeName)) { - return this.modes.get(modeName).getEmissionsPerPersonByNumberOfPassengers(numberOfPassengers); + public double getCo2EmissionsByModeAndDistanceAndNumberOfPassengers( + String modeName, + int numberOfPassengers, + double distanceInKm + ) { + if (this.modes.containsKey(modeName) && distanceInKm >= 0) { + return ( + this.modes.get(modeName).getEmissionsPerPersonByNumberOfPassengers(numberOfPassengers) * + distanceInKm + ); } - return 0; + return -1; } } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java index 9c69cc67dad..41ec8d31871 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java @@ -28,7 +28,7 @@ public DigitransitEmissionsMode(String mode, String avg, int p_avg) { this.avg = Float.parseFloat(avg); } catch (NumberFormatException e) { LOG.warn("Converting Digitransit emissions average value failed.", e); - this.avg = 0.0F; + this.avg = -1; } this.averageCo2EmissionsPerPersonPerKm = getEmissionsPerPersonByNumberOfPassengers(p_avg); } @@ -50,6 +50,6 @@ public float getAverageCo2EmissionsPerPersonPerKm() { } public float getEmissionsPerPersonByNumberOfPassengers(int numberOfPassengers) { - return Math.round(this.avg / numberOfPassengers); + return this.avg >= 0 ? Math.round(this.avg / numberOfPassengers) : -1; } } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java index 1aa54e23dd4..d1429907fc2 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java @@ -9,7 +9,6 @@ import org.opentripplanner.model.plan.StreetLeg; import org.opentripplanner.model.plan.TransitLeg; import org.opentripplanner.street.search.TraverseMode; -import org.opentripplanner.transit.model.framework.FeedScopedId; @Sandbox public class DigitransitEmissionsService implements EmissionsService { @@ -46,7 +45,7 @@ public DigitransitEmissionsAgency getEmissionsByAgencyId(String agencyId) { return null; } - public float getEmissionsForRoute(Itinerary itinerary) { + public Float getEmissionsForRoute(Itinerary itinerary) { List transitLegs = itinerary .getLegs() .stream() @@ -55,7 +54,7 @@ public float getEmissionsForRoute(Itinerary itinerary) { .toList(); if (!transitLegs.isEmpty()) { - return getEmissionsForTransitRoute(transitLegs); + return (float) getEmissionsForTransitRoute(transitLegs); } List carLegs = itinerary @@ -67,43 +66,43 @@ public float getEmissionsForRoute(Itinerary itinerary) { .toList(); if (!carLegs.isEmpty()) { - return getEmissionsForCarRoute(carLegs); + return (float) getEmissionsForCarRoute(carLegs); } - - return 0.0F; + return null; } - private float getEmissionsForTransitRoute(List transitLegs) { - return (float) transitLegs + private double getEmissionsForTransitRoute(List transitLegs) { + return transitLegs .stream() .mapToDouble(leg -> { - FeedScopedId agencyIdFeedScoped = leg.getAgency().getId(); double legDistanceInKm = leg.getDistanceMeters() / 1000; DigitransitEmissionsAgency digitransitEmissionsAgency = getEmissionsByAgencyId( - agencyIdFeedScoped.getId() + leg.getAgency().getId().getId() ); - float emissionsForAgencyPerKm = digitransitEmissionsAgency != null - ? digitransitEmissionsAgency.getAverageCo2EmissionsByModePerPerson(leg.getMode().name()) - : 0; - double emissionsPerLeg = legDistanceInKm * emissionsForAgencyPerKm; - return emissionsPerLeg; + return digitransitEmissionsAgency != null + ? digitransitEmissionsAgency.getAverageCo2EmissionsByModeAndDistancePerPerson( + leg.getMode().name(), + legDistanceInKm + ) + : -1; }) .sum(); } - private float getEmissionsForCarRoute(List carLegs) { - return (float) carLegs + private double getEmissionsForCarRoute(List carLegs) { + return carLegs .stream() .mapToDouble(leg -> { DigitransitEmissionsAgency digitransitEmissionsAgency = getEmissionsByAgencyId( TraverseMode.CAR.toString() ); - float avgCarEmissions = digitransitEmissionsAgency.getAverageCo2EmissionsByModePerPerson( - leg.getMode().name() - ); double carLegDistanceInKm = leg.getDistanceMeters() / 1000; - double carEmissionsPerLeg = carLegDistanceInKm * avgCarEmissions; - return carEmissionsPerLeg; + return digitransitEmissionsAgency != null + ? digitransitEmissionsAgency.getAverageCo2EmissionsByModeAndDistancePerPerson( + leg.getMode().name(), + carLegDistanceInKm + ) + : -1; }) .sum(); } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java index 934632ff129..cf7ddabf020 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java @@ -17,7 +17,7 @@ public List filter(List itineraries) { return itineraries .stream() .peek(i -> { - float emissions = emissionsService.getEmissionsForRoute(i); + Float emissions = emissionsService.getEmissionsForRoute(i); if (Objects.nonNull(emissions)) { i.setEmissions(emissions); } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java index a0a824db858..72ed09d26d0 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java @@ -9,5 +9,5 @@ public interface EmissionsService extends Serializable { HashMap getEmissionByAgency(); - float getEmissionsForRoute(Itinerary itinerary); + Float getEmissionsForRoute(Itinerary itinerary); } From 740e0c888a56e6e667eb0f96f603b94089b18a3d Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 3 Aug 2023 17:22:11 +0300 Subject: [PATCH 10/68] generated documentation fixed --- docs/BuildConfiguration.md | 32 +++++++++++-------- .../emissions/EmissionsConfig.java | 17 ++++++++-- .../framework/application/OTPFeature.java | 2 +- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 30c3520f5cf..9a495f646ef 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -56,6 +56,8 @@ Sections follow that describe particular settings in more depth. |       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | | demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | |    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | +| [digitransitEmissions](#digitransitEmissions) | `object` | Configure properties for emissions file. | *Optional* | | 2.4 | +|    url | `string` | Url to emissions json file. | *Optional* | `""` | 2.4 | | [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | | [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | | gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | @@ -64,7 +66,6 @@ Sections follow that describe particular settings in more depth. |    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | |    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | |    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | -| [DigitransitEmissions](#digitransitEmissions) | `object[]` | Configure properties for emissions file. | *Optional* | | 2.2 | | islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | |    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | |    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | @@ -719,6 +720,18 @@ values are defined in meters in the source data. If, for example, decimetres are in the source data, this should be set to 0.1. +

digitransitEmissions

+ +**Since version:** `2.4` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` +**Path:** / + +Configure properties for emissions file. + +By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) +emissions associated with various transportation modes and routes. This data is then used +for perform emission calculations to different transport modes and car. + +

elevationBucket

**Since version:** `na` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` @@ -768,18 +781,6 @@ This parameter sets the generic level of preference. What is the actual cost can with the `stopTransferCost` parameter in the router configuration. -

DigitransitEmissions

- -**Since version:** `2.4` ∙ **Type:** `object[]` ∙ **Cardinality:** `Optional` -**Path:** / - -Configure properties for emissions file. - -By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) -emissions associated with various transportation modes and routes. This data is then used for perform -emission calculations to different transport modes and car. - -

adaptivePruningDistance

**Since version:** `2.3` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `250` @@ -1197,7 +1198,10 @@ case where this is not the case. "enabled" : true } } - ] + ], + "digitransitEmissions" : { + "url" : "foo.bar" + } } ``` diff --git a/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java b/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java index 82a16174e21..5e47c2cb80c 100644 --- a/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java +++ b/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java @@ -13,9 +13,20 @@ public class EmissionsConfig { private String configName; public EmissionsConfig(String parameterName, NodeAdapter root) { - var c = root.of(parameterName).since(V2_4).summary("Configuration for Emissions").asObject(); - - this.url = c.of("url").since(V2_4).description("Url to emissions json file").asString(""); + var c = root + .of(parameterName) + .since(V2_4) + .summary("Configure properties for emissions file.") + .description( + """ + By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) + emissions associated with various transportation modes and routes. This data is then used + for perform emission calculations to different transport modes and car. + """ + ) + .asObject(); + + this.url = c.of("url").since(V2_4).summary("Url to emissions json file.").asString(""); this.configName = parameterName; } diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 3c2b892a4a8..6399a22d9e4 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -30,7 +30,7 @@ public enum OTPFeature { DebugClient(true, false, "Enable the debug web client located at the root of the web server."), FloatingBike(true, false, "Enable floating bike routing."), GtfsGraphQlApi(true, true, "Enable GTFS GraphQL API."), - co2Emissions(false, true, "Enable emissions"), + co2Emissions(false, true, "Enable emissions calculation and data handling."), /** * If this feature flag is switched on, then the minimum transfer time is not the minimum transfer * time, but the definitive transfer time. Use this to override what we think the transfer will From 4f5607961aeb616dabfc91c6a132ab5bcd63e719 Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 31 Aug 2023 16:26:02 +0300 Subject: [PATCH 11/68] Implementation using dagger injection --- docs/BuildConfiguration.md | 4 +- docs/Configuration.md | 2 +- docs/sandbox/DigitransitEmissions.md | 9 +- .../DigitransitEmissionsAgencyTest.java | 50 -------- .../DigitransitEmissionsModeTest.java | 20 ---- .../EmissionsServiceTest.java} | 30 +++-- .../mapping/TripRequestMapperTest.java | 3 + .../DefaultEmissionsService.java | 26 +++++ .../DefaultEmissionsServiceRepository.java | 26 +++++ .../DigitransitEmissions.java | 14 +++ .../DigitransitEmissionsConfig.java} | 18 +-- .../DigitransitEmissionsModule.java | 81 +++++++++++++ .../DigitransitEmissionsService.java | 93 +++++++++++++++ .../digitransitemissions/EmissionsFilter.java | 20 ++++ .../EmissionsService.java | 9 ++ .../EmissionsServiceModule.java | 14 +++ .../EmissionsServiceRepository.java | 11 ++ .../EmissionsServiceRepositoryModule.java | 10 ++ .../ext/emissions/DigitransitEmissions.java | 79 ------------- .../emissions/DigitransitEmissionsAgency.java | 89 -------------- .../emissions/DigitransitEmissionsMode.java | 55 --------- .../DigitransitEmissionsService.java | 109 ------------------ .../ext/emissions/EmissionsFilter.java | 27 ----- .../ext/emissions/EmissionsService.java | 13 --- .../emissions/EmissionsModule.java | 73 ------------ .../framework/application/OTPFeature.java | 3 +- .../graph_builder/GraphBuilder.java | 5 +- .../module/configure/GraphBuilderFactory.java | 10 +- .../module/configure/GraphBuilderModules.java | 10 +- .../opentripplanner/model/plan/Itinerary.java | 7 +- .../ItineraryListFilterChainBuilder.java | 12 +- .../RouteRequestToFilterChainMapper.java | 7 +- .../opentripplanner/routing/graph/Graph.java | 10 -- .../routing/graph/SerializedGraphObject.java | 6 +- .../opentripplanner/standalone/OTPMain.java | 3 +- .../api/OtpServerRequestContext.java | 3 + .../standalone/config/BuildConfig.java | 6 +- .../configure/ConstructApplication.java | 10 +- .../ConstructApplicationFactory.java | 10 ++ .../configure/ConstructApplicationModule.java | 5 +- .../standalone/configure/LoadApplication.java | 13 ++- .../configure/LoadApplicationFactory.java | 6 + .../server/DefaultServerRequestContext.java | 11 ++ .../transit/service/TransitModel.java | 11 -- .../opentripplanner/TestServerContext.java | 8 ++ .../routing/graph/GraphSerializationTest.java | 14 ++- .../transit/speed_test/SpeedTest.java | 3 + 47 files changed, 449 insertions(+), 609 deletions(-) delete mode 100644 src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java delete mode 100644 src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java rename src/ext-test/java/org/opentripplanner/ext/emissions/{DigitransitEmissionsServiceTest.java => digitransit/EmissionsServiceTest.java} (79%) create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsServiceRepository.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java rename src/{main/java/org/opentripplanner/emissions/EmissionsConfig.java => ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java} (54%) create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepository.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepositoryModule.java delete mode 100644 src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java delete mode 100644 src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java delete mode 100644 src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java delete mode 100644 src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java delete mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java delete mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java delete mode 100644 src/main/java/org/opentripplanner/emissions/EmissionsModule.java diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 9a495f646ef..d2acf3ad776 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -728,8 +728,8 @@ in the source data, this should be set to 0.1. Configure properties for emissions file. By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) -emissions associated with various transportation modes and routes. This data is then used -for perform emission calculations to different transport modes and car. +emissions associated with transportation modes. This data is then used +to perform emission calculations for public transport modes and car travel.

elevationBucket

diff --git a/docs/Configuration.md b/docs/Configuration.md index 4afb51775c5..c83f808fd6c 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -228,13 +228,13 @@ Here is a list of all features which can be toggled on/off and their default val | `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | | `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | ✓️ | -| `co2Emissions` | Enable emissions calculation and data handling. | | ✓️ | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | | `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | | `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | | `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | | `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | | `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | +| `Co2Emissions` | Enable emissions calculation and data handling. | | ✓️ | | `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | | `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | | `FlexRouting` | Enable FLEX routing. | | ✓️ | diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md index e8a33addd59..4563ad43bb2 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/DigitransitEmissions.md @@ -32,12 +32,11 @@ For example: ] ``` -Emissions data is loaded from the provided source and embedded in graph building process -as part of the itinerary. +Emissions data is loaded from the provided source and embedded into the graph during the build process. ### Configuration -To enable this functionality, you need to add the "co2Emissions" feature in the +To enable this functionality, you need to add the "Co2Emissions" feature in the `otp-config.json` file. Include the `digitransitEmissions` object in the `build-config.json` file. The `digitransitEmissions` object should contain a parameter called `url`, which should be set to the location where the emissions data is stored. @@ -45,7 +44,7 @@ which should be set to the location where the emissions data is stored. ```json //otp-config.json { - "co2Emissions": true + "Co2Emissions": true } ``` include the `digitransitEmissions` object in the @@ -64,6 +63,6 @@ which should be set to the location where the emissions data is stored. ### OTP 2.4 -- Initial implementation of Emissions calculation +- Initial implementation of the emissions calculation. diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java deleted file mode 100644 index 7c8e519b5de..00000000000 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgencyTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.opentripplanner.ext.emissions; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model.basic.TransitMode; - -class DigitransitEmissionsAgencyTest { - - private DigitransitEmissionsAgency agency; - - @BeforeEach - protected void setUp() throws Exception { - DigitransitEmissionsMode mode = new DigitransitEmissionsMode("BUS", "120", 12); - this.agency = new DigitransitEmissionsAgency("db", "FOO", "FOO_COMP"); - - this.agency.addMode(mode); - } - - @Test - void testGetModeDefaultBehavior() { - assertEquals("BUS", this.agency.getMode(TransitMode.BUS).getName()); - } - - @Test - void testGetModeNull() { - assertNull(this.agency.getMode(TransitMode.AIRPLANE)); - } - - @Test - void getAverageCo2EmissionsByModeAndDistancePerPerson() { - assertEquals(5, this.agency.getAverageCo2EmissionsByModeAndDistancePerPerson("BUS", 0.5)); - } - - @Test - void getAverageCo2EmissionsByModeAndDistancePerPersonNullMode() { - assertEquals(-1, this.agency.getAverageCo2EmissionsByModeAndDistancePerPerson("AIRPLANE", 1)); - } - - @Test - void getAverageCo2EmissionsByModeAndDistancePerPersonZeroDistance() { - assertEquals(0, this.agency.getAverageCo2EmissionsByModeAndDistancePerPerson("BUS", 0)); - } - - @Test - void getAverageCo2EmissionsByModeAndDistancePerPersonNegativeDistance() { - assertEquals(-1, this.agency.getAverageCo2EmissionsByModeAndDistancePerPerson("BUS", -1)); - } -} diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java deleted file mode 100644 index 6fb6d779144..00000000000 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsModeTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.opentripplanner.ext.emissions; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; - -class DigitransitEmissionsModeTest { - - @Test - void EmissionsModeWithNormalAvg() { - DigitransitEmissionsMode mode = new DigitransitEmissionsMode("BUS", "120", 12); - assertEquals(120.0, mode.getAvg()); - } - - @Test - void EmissionsModeWithIncorrectAvg() { - DigitransitEmissionsMode mode = new DigitransitEmissionsMode("BUS", "Foo", 12); - assertEquals(-1, mode.getAvg()); - } -} diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java similarity index 79% rename from src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java rename to src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index a42b81115ec..a859eaa76ba 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/DigitransitEmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.emissions; +package org.opentripplanner.ext.emissions.digitransit; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; @@ -7,10 +7,14 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.ext.digitransitemissions.DigitransitEmissions; +import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; @@ -25,9 +29,9 @@ import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; -class DigitransitEmissionsServiceTest { +class EmissionsServiceTest { - private DigitransitEmissionsService eservice; + private DigitransitEmissionsService eService; static final ZonedDateTime TIME = OffsetDateTime .parse("2023-07-20T17:49:06+03:00") @@ -41,20 +45,14 @@ class DigitransitEmissionsServiceTest { @BeforeEach void SetUp() { - DigitransitEmissions e = new DigitransitEmissions("db", "F:1", "Foo_CO", "BUS", "120", 12); - DigitransitEmissions[] list = new DigitransitEmissions[2]; - list[0] = e; - list[1] = new DigitransitEmissions("db", "CAR", "CAR", "CAR", "1100", 1); - this.eservice = new DigitransitEmissionsService(list); + Map digitransitEmissions = new HashMap<>(); + digitransitEmissions.put("F:F:1:BUS", new DigitransitEmissions(120, 12)); + digitransitEmissions.put("CAR", new DigitransitEmissions(1100, 1)); + this.eService = new DigitransitEmissionsService(digitransitEmissions); } @Test - void getEmissionsByAgencyId() { - assertEquals("Foo_CO", eservice.getEmissionsByAgencyId("F:1").getAgency_name()); - } - - @Test - void getEmissionsForRoute() { + void getEmissionsForItinerary() { var route = TransitModelForTest.route(id("2")).withAgency(subject).build(); List legs = new ArrayList<>(); var pattern = TransitModelForTest @@ -85,7 +83,7 @@ void getEmissionsForRoute() { ); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(0, eservice.getEmissionsForRoute(i)); + assertEquals(0, eService.getEmissionsForItinerary(i)); } @Test @@ -113,6 +111,6 @@ void getEmissionsForCarRoute() { .build(); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(235.83999633789062F, eservice.getEmissionsForRoute(i)); + assertEquals(235.83999633789062F, eService.getEmissionsForItinerary(i)); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java index 7eb2554a673..ae0125c9bc9 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java @@ -20,6 +20,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsService; +import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsServiceRepository; import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.raptor.configure.RaptorConfig; @@ -75,6 +77,7 @@ public class TripRequestMapperTest implements PlanTestConstants { new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()), new DefaultVehiclePositionService(), new DefaultVehicleRentalService(), + new DefaultEmissionsService(new DefaultEmissionsServiceRepository()), RouterConfig.DEFAULT.flexConfig(), List.of(), null diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java new file mode 100644 index 00000000000..2e283fcbcba --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java @@ -0,0 +1,26 @@ +package org.opentripplanner.ext.digitransitemissions; + +import jakarta.inject.Inject; +import java.io.Serializable; +import java.util.Optional; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.model.plan.Itinerary; + +public class DefaultEmissionsService implements Serializable, EmissionsService { + + private EmissionsServiceRepository repository = null; + + @Inject + public DefaultEmissionsService(EmissionsServiceRepository repository) { + this.repository = repository; + } + + @Override + public Float getEmissionsForItinerary(Itinerary itinerary) { + Optional service = repository.retrieveEmissionsService(); + if (service.isPresent()) { + return service.get().getEmissionsForItinerary(itinerary); + } + return null; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsServiceRepository.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsServiceRepository.java new file mode 100644 index 00000000000..a23253024cb --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsServiceRepository.java @@ -0,0 +1,26 @@ +package org.opentripplanner.ext.digitransitemissions; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import java.io.Serializable; +import java.util.Optional; +import javax.annotation.Nonnull; + +@Singleton +public class DefaultEmissionsServiceRepository implements EmissionsServiceRepository, Serializable { + + private volatile DigitransitEmissionsService emissionsService = null; + + @Inject + public DefaultEmissionsServiceRepository() {} + + @Override + public Optional retrieveEmissionsService() { + return Optional.ofNullable(emissionsService); + } + + @Override + public void saveEmissionsService(@Nonnull DigitransitEmissionsService emissionsService) { + this.emissionsService = emissionsService; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java new file mode 100644 index 00000000000..e5f32b2ee14 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java @@ -0,0 +1,14 @@ +package org.opentripplanner.ext.digitransitemissions; + +import java.util.Objects; + +public record DigitransitEmissions(double avg, int passengerAvg) { + public DigitransitEmissions { + Objects.requireNonNull(avg); + Objects.requireNonNull(passengerAvg); + } + + public double getEmissionsPerPassenger() { + return this.avg / this.passengerAvg; + } +} diff --git a/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java similarity index 54% rename from src/main/java/org/opentripplanner/emissions/EmissionsConfig.java rename to src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java index 5e47c2cb80c..07f733e8210 100644 --- a/src/main/java/org/opentripplanner/emissions/EmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java @@ -1,18 +1,17 @@ -package org.opentripplanner.emissions; +package org.opentripplanner.ext.digitransitemissions; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_4; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; /** - * This class is responsible for mapping OSM configuration into OSM parameters. + * This class is responsible for mapping Digitransit emissions configuration into Digitransit emissions parameters. */ -public class EmissionsConfig { +public class DigitransitEmissionsConfig { private String url; - private String configName; - public EmissionsConfig(String parameterName, NodeAdapter root) { + public DigitransitEmissionsConfig(String parameterName, NodeAdapter root) { var c = root .of(parameterName) .since(V2_4) @@ -20,21 +19,16 @@ public EmissionsConfig(String parameterName, NodeAdapter root) { .description( """ By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) - emissions associated with various transportation modes and routes. This data is then used - for perform emission calculations to different transport modes and car. + emissions associated with transportation modes. This data is then used + to perform emission calculations for public transport modes and car travel. """ ) .asObject(); this.url = c.of("url").since(V2_4).summary("Url to emissions json file.").asString(""); - this.configName = parameterName; } public String getUrl() { return url; } - - public String getConfigName() { - return configName; - } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java new file mode 100644 index 00000000000..e99d204e910 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java @@ -0,0 +1,81 @@ +package org.opentripplanner.ext.digitransitemissions; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import dagger.Module; +import jakarta.inject.Inject; +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.framework.io.OtpHttpClient; +import org.opentripplanner.graph_builder.model.GraphBuilderModule; +import org.opentripplanner.standalone.config.BuildConfig; +import org.opentripplanner.street.search.TraverseMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Module +public class DigitransitEmissionsModule implements GraphBuilderModule { + + private static final Logger LOG = LoggerFactory.getLogger(DigitransitEmissionsModule.class); + private BuildConfig config; + private EmissionsServiceRepository emissionsServiceRepository; + + @Inject + public DigitransitEmissionsModule( + BuildConfig config, + EmissionsServiceRepository emissionsServiceRepository + ) { + this.config = config; + this.emissionsServiceRepository = emissionsServiceRepository; + } + + public void buildGraph() { + if (config.digitransitEmissions != null) { + LOG.info("Start emissions building!"); + String url = config.digitransitEmissions.getUrl(); + + if (url != null && !url.isEmpty()) { + LOG.info("Fetching data from {}", url); + try { + var http = new OtpHttpClient(); + JsonNode data = http.getAndMapAsJsonNode(new URI(url), Map.of(), new ObjectMapper()); + if (data == null) { + throw new IOException("Did not find any emissions data from url " + url); + } + Map digitransitEmissions = parseDigitransitEmissions(data); + this.emissionsServiceRepository.saveEmissionsService( + new DigitransitEmissionsService(digitransitEmissions) + ); + } catch (Exception e) { + LOG.error("ERROR ", e); + } + } + } + } + + private Map parseDigitransitEmissions(JsonNode data) { + Map digitransitEmissions = new HashMap<>(); + for (JsonNode line : data) { + digitransitEmissions.put( + getKey(line), + new DigitransitEmissions(line.get("avg").asDouble(), line.get("p_avg").intValue()) + ); + } + return digitransitEmissions; + } + + private String getKey(JsonNode data) { + if (data.get("mode").asText().equals(TraverseMode.CAR.toString())) { + return TraverseMode.CAR.toString(); + } + String key = + data.get("db").asText() + + ":" + + data.get("agency_id").asText() + + ":" + + data.get("mode").asText(); + return key; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java new file mode 100644 index 00000000000..4560147a4e1 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java @@ -0,0 +1,93 @@ +package org.opentripplanner.ext.digitransitemissions; + +import java.io.Serializable; +import java.util.DoubleSummaryStatistics; +import java.util.List; +import java.util.Map; +import java.util.stream.DoubleStream; +import org.opentripplanner.ext.flex.FlexibleTransitLeg; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.ScheduledTransitLeg; +import org.opentripplanner.model.plan.StreetLeg; +import org.opentripplanner.model.plan.TransitLeg; +import org.opentripplanner.street.search.TraverseMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +@Sandbox +public class DigitransitEmissionsService implements Serializable, EmissionsService { + + private Map emissions; + + public DigitransitEmissionsService(Map emissions) { + this.emissions = emissions; + } + + @Override + public Float getEmissionsForItinerary(Itinerary itinerary) { + List transitLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg) + .map(TransitLeg.class::cast) + .toList(); + + if (!transitLegs.isEmpty()) { + return (float) getEmissionsForTransitItinerary(transitLegs); + } + + List carLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof StreetLeg) + .map(StreetLeg.class::cast) + .filter(leg -> leg.getMode() == TraverseMode.CAR) + .toList(); + + if (!carLegs.isEmpty()) { + float emis = (float) getEmissionsForCarItinerary(carLegs); + return emis; + } + return null; + } + + private double getEmissionsForTransitItinerary(List transitLegs) { + DoubleStream emissionsStream = transitLegs + .stream() + .mapToDouble(leg -> { + double legDistanceInKm = leg.getDistanceMeters() / 1000; + FeedScopedId feedScopedAgencyId = leg.getAgency().getId(); + String modeName = leg.getMode().name(); + String key = feedScopedAgencyId + ":" + modeName; + + if (key != null && this.emissions.containsKey(key)) { + return this.emissions.get(key).getEmissionsPerPassenger() * legDistanceInKm; + } + return -1; + }); + DoubleSummaryStatistics stats = emissionsStream.summaryStatistics(); + if (stats.getMin() < 0) { + return -1; + } + Double sum = stats.getSum(); + + return sum; + } + + private double getEmissionsForCarItinerary(List carLegs) { + if (this.emissions.containsKey(TraverseMode.CAR.toString())) { + double emis = carLegs + .stream() + .mapToDouble(leg -> { + double carLegDistanceInKm = leg.getDistanceMeters() / 1000; + return ( + this.emissions.get(TraverseMode.CAR.toString()).getEmissionsPerPassenger() * + carLegDistanceInKm + ); + }) + .sum(); + return emis; + } + return -1; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java new file mode 100644 index 00000000000..734f02e4732 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java @@ -0,0 +1,20 @@ +package org.opentripplanner.ext.digitransitemissions; + +import java.util.List; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; + +@Sandbox +public record EmissionsFilter(EmissionsService emissionsService) implements ItineraryListFilter { + @Override + public List filter(List itineraries) { + for (Itinerary i : itineraries) { + Float emissions = emissionsService.getEmissionsForItinerary(i); + if (emissions != null) { + i.setEmissions(emissions); + } + } + return itineraries; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java new file mode 100644 index 00000000000..60d48dbf5f6 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java @@ -0,0 +1,9 @@ +package org.opentripplanner.ext.digitransitemissions; + +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.model.plan.Itinerary; + +@Sandbox +public interface EmissionsService { + Float getEmissionsForItinerary(Itinerary itinerary); +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java new file mode 100644 index 00000000000..91c8b7aaafa --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java @@ -0,0 +1,14 @@ +package org.opentripplanner.ext.digitransitemissions; + +import dagger.Binds; +import dagger.Module; + +/** + * The service is used during application serve phase, not loading, so we need to provide + * a module for the service without the repository, which is injected from the loading phase. + */ +@Module +public interface EmissionsServiceModule { + @Binds + EmissionsService bindService(DefaultEmissionsService service); +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepository.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepository.java new file mode 100644 index 00000000000..f6fcf88935c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepository.java @@ -0,0 +1,11 @@ +package org.opentripplanner.ext.digitransitemissions; + +import java.io.Serializable; +import java.util.Optional; +import javax.annotation.Nonnull; + +public interface EmissionsServiceRepository extends Serializable { + Optional retrieveEmissionsService(); + + void saveEmissionsService(@Nonnull DigitransitEmissionsService emissionsService); +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepositoryModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepositoryModule.java new file mode 100644 index 00000000000..f14c6f77d66 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepositoryModule.java @@ -0,0 +1,10 @@ +package org.opentripplanner.ext.digitransitemissions; + +import dagger.Binds; +import dagger.Module; + +@Module +public interface EmissionsServiceRepositoryModule { + @Binds + EmissionsServiceRepository bindRepository(DefaultEmissionsServiceRepository repository); +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java deleted file mode 100644 index 38c9c522906..00000000000 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissions.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.opentripplanner.ext.emissions; - -import java.io.Serializable; -import org.opentripplanner.framework.lang.Sandbox; - -@Sandbox -public class DigitransitEmissions implements Serializable { - - private String db; - private String agency_id; - private String agency_name; - private String mode; - private String avg; - private int p_avg; - - public DigitransitEmissions( - String db, - String agency_id, - String agency_name, - String mode, - String avg, - int p_avg - ) { - this.db = db; - this.agency_id = agency_id; - this.agency_name = agency_name; - this.mode = mode; - this.avg = avg; - this.p_avg = p_avg; - } - - public String getDb() { - return db; - } - - public void setDb(String db) { - this.db = db; - } - - public String getAgency_id() { - return agency_id; - } - - public void setAgency_id(String agency_id) { - this.agency_id = agency_id; - } - - public String getAgency_name() { - return agency_name; - } - - public void setAgency_name(String agency_name) { - this.agency_name = agency_name; - } - - public String getMode() { - return mode; - } - - public void setMode(String mode) { - this.mode = mode; - } - - public String getAvg() { - return avg; - } - - public void setAvg(String avg) { - this.avg = avg; - } - - public int getP_avg() { - return p_avg; - } - - public void setP_avg(int p_avg) { - this.p_avg = p_avg; - } -} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java deleted file mode 100644 index a5b23d10844..00000000000 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsAgency.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.opentripplanner.ext.emissions; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import org.opentripplanner.framework.lang.Sandbox; -import org.opentripplanner.transit.model.basic.TransitMode; - -@Sandbox -public class DigitransitEmissionsAgency implements Serializable { - - private String db; - private String agency_id; - private String agency_name; - private Map modes; - - public DigitransitEmissionsAgency(String db, String agency_id, String agency_name) { - this.db = db; - this.agency_id = agency_id; - this.agency_name = agency_name; - this.modes = new HashMap<>(); - } - - public String getDb() { - return db; - } - - public String getAgency_id() { - return agency_id; - } - - public String getAgency_name() { - return agency_name; - } - - public Map getModes() { - return modes; - } - - public void setModes(Map modes) { - this.modes = modes; - } - - public void addMode(DigitransitEmissionsMode mode) { - this.modes.put(mode.getName(), mode); - } - - public DigitransitEmissionsMode getMode(TransitMode mode) { - if (this.modes.containsKey(mode.name())) { - return this.modes.get(mode.name()); - } - return null; - } - - /** - * Returns the average CO2 emissions (g/km) per person for a specific transit mode. - * @param modeName name of transit mode - * @return CO2 emissions (g/km) per person - */ - public double getAverageCo2EmissionsByModeAndDistancePerPerson( - String modeName, - double distanceInKm - ) { - if (this.modes.containsKey(modeName) && distanceInKm >= 0) { - return this.modes.get(modeName).getAverageCo2EmissionsPerPersonPerKm() * distanceInKm; - } - return -1; - } - - /** - * Returns the CO2 emissions (g/km) per person for a specific transit mode and the number of - * passengers. - * @param modeName name of transit mode - * @return CO2 emissions (g/km) per person - */ - public double getCo2EmissionsByModeAndDistanceAndNumberOfPassengers( - String modeName, - int numberOfPassengers, - double distanceInKm - ) { - if (this.modes.containsKey(modeName) && distanceInKm >= 0) { - return ( - this.modes.get(modeName).getEmissionsPerPersonByNumberOfPassengers(numberOfPassengers) * - distanceInKm - ); - } - return -1; - } -} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java deleted file mode 100644 index 41ec8d31871..00000000000 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsMode.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.opentripplanner.ext.emissions; - -import java.io.Serializable; -import org.opentripplanner.framework.lang.Sandbox; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Sandbox -public class DigitransitEmissionsMode implements Serializable { - - private String name; - private float avg; - private int p_avg; - private float averageCo2EmissionsPerPersonPerKm; - - private static final Logger LOG = LoggerFactory.getLogger(DigitransitEmissionsMode.class); - - /** - * @param mode transit mode name - * @param avg average CO2 emissions in grams per kilometre - * @param p_avg average number of passengers per vehicle - */ - public DigitransitEmissionsMode(String mode, String avg, int p_avg) { - this.name = mode; - this.p_avg = p_avg; - - try { - this.avg = Float.parseFloat(avg); - } catch (NumberFormatException e) { - LOG.warn("Converting Digitransit emissions average value failed.", e); - this.avg = -1; - } - this.averageCo2EmissionsPerPersonPerKm = getEmissionsPerPersonByNumberOfPassengers(p_avg); - } - - public String getName() { - return name; - } - - public double getAvg() { - return avg; - } - - public int getP_avg() { - return p_avg; - } - - public float getAverageCo2EmissionsPerPersonPerKm() { - return this.averageCo2EmissionsPerPersonPerKm; - } - - public float getEmissionsPerPersonByNumberOfPassengers(int numberOfPassengers) { - return this.avg >= 0 ? Math.round(this.avg / numberOfPassengers) : -1; - } -} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java deleted file mode 100644 index d1429907fc2..00000000000 --- a/src/ext/java/org/opentripplanner/ext/emissions/DigitransitEmissionsService.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.opentripplanner.ext.emissions; - -import java.util.HashMap; -import java.util.List; -import org.opentripplanner.ext.flex.FlexibleTransitLeg; -import org.opentripplanner.framework.lang.Sandbox; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.ScheduledTransitLeg; -import org.opentripplanner.model.plan.StreetLeg; -import org.opentripplanner.model.plan.TransitLeg; -import org.opentripplanner.street.search.TraverseMode; - -@Sandbox -public class DigitransitEmissionsService implements EmissionsService { - - private HashMap emissionByAgency; - - public DigitransitEmissionsService(DigitransitEmissions[] emissions) { - this.emissionByAgency = new HashMap<>(); - for (DigitransitEmissions e : emissions) { - DigitransitEmissionsMode mode = new DigitransitEmissionsMode( - e.getMode(), - e.getAvg(), - e.getP_avg() - ); - if (!this.emissionByAgency.containsKey(e.getAgency_id())) { - this.emissionByAgency.put( - e.getAgency_id(), - new DigitransitEmissionsAgency(e.getDb(), e.getAgency_id(), e.getAgency_name()) - ); - } - this.emissionByAgency.get(e.getAgency_id()).addMode(mode); - } - } - - @Override - public HashMap getEmissionByAgency() { - return emissionByAgency; - } - - public DigitransitEmissionsAgency getEmissionsByAgencyId(String agencyId) { - if (agencyId != null && this.emissionByAgency.containsKey(agencyId)) { - return this.emissionByAgency.get(agencyId); - } - return null; - } - - public Float getEmissionsForRoute(Itinerary itinerary) { - List transitLegs = itinerary - .getLegs() - .stream() - .filter(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg) - .map(TransitLeg.class::cast) - .toList(); - - if (!transitLegs.isEmpty()) { - return (float) getEmissionsForTransitRoute(transitLegs); - } - - List carLegs = itinerary - .getLegs() - .stream() - .filter(l -> l instanceof StreetLeg) - .map(StreetLeg.class::cast) - .filter(leg -> leg.getMode() == TraverseMode.CAR) - .toList(); - - if (!carLegs.isEmpty()) { - return (float) getEmissionsForCarRoute(carLegs); - } - return null; - } - - private double getEmissionsForTransitRoute(List transitLegs) { - return transitLegs - .stream() - .mapToDouble(leg -> { - double legDistanceInKm = leg.getDistanceMeters() / 1000; - DigitransitEmissionsAgency digitransitEmissionsAgency = getEmissionsByAgencyId( - leg.getAgency().getId().getId() - ); - return digitransitEmissionsAgency != null - ? digitransitEmissionsAgency.getAverageCo2EmissionsByModeAndDistancePerPerson( - leg.getMode().name(), - legDistanceInKm - ) - : -1; - }) - .sum(); - } - - private double getEmissionsForCarRoute(List carLegs) { - return carLegs - .stream() - .mapToDouble(leg -> { - DigitransitEmissionsAgency digitransitEmissionsAgency = getEmissionsByAgencyId( - TraverseMode.CAR.toString() - ); - double carLegDistanceInKm = leg.getDistanceMeters() / 1000; - return digitransitEmissionsAgency != null - ? digitransitEmissionsAgency.getAverageCo2EmissionsByModeAndDistancePerPerson( - leg.getMode().name(), - carLegDistanceInKm - ) - : -1; - }) - .sum(); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java deleted file mode 100644 index cf7ddabf020..00000000000 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.opentripplanner.ext.emissions; - -import java.util.List; -import java.util.Objects; -import org.opentripplanner.framework.lang.Sandbox; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Sandbox -public record EmissionsFilter(EmissionsService emissionsService) implements ItineraryListFilter { - private static final Logger LOG = LoggerFactory.getLogger(EmissionsFilter.class); - - @Override - public List filter(List itineraries) { - return itineraries - .stream() - .peek(i -> { - Float emissions = emissionsService.getEmissionsForRoute(i); - if (Objects.nonNull(emissions)) { - i.setEmissions(emissions); - } - }) - .toList(); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java deleted file mode 100644 index 72ed09d26d0..00000000000 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.opentripplanner.ext.emissions; - -import java.io.Serializable; -import java.util.HashMap; -import org.opentripplanner.framework.lang.Sandbox; -import org.opentripplanner.model.plan.Itinerary; - -@Sandbox -public interface EmissionsService extends Serializable { - HashMap getEmissionByAgency(); - - Float getEmissionsForRoute(Itinerary itinerary); -} diff --git a/src/main/java/org/opentripplanner/emissions/EmissionsModule.java b/src/main/java/org/opentripplanner/emissions/EmissionsModule.java deleted file mode 100644 index 67b81bdaf27..00000000000 --- a/src/main/java/org/opentripplanner/emissions/EmissionsModule.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.opentripplanner.emissions; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; -import java.io.IOException; -import java.net.URI; -import java.util.Map; -import org.opentripplanner.ext.emissions.DigitransitEmissions; -import org.opentripplanner.ext.emissions.DigitransitEmissionsService; -import org.opentripplanner.ext.emissions.EmissionsService; -import org.opentripplanner.framework.io.OtpHttpClient; -import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; -import org.opentripplanner.graph_builder.model.GraphBuilderModule; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.standalone.config.BuildConfig; -import org.opentripplanner.transit.service.TransitModel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class EmissionsModule implements GraphBuilderModule { - - private static final Logger LOG = LoggerFactory.getLogger(EmissionsModule.class); - - private TransitModel transitModel; - private Graph graph; - private DataImportIssueStore issueStore; - private BuildConfig config; - - public EmissionsModule( - TransitModel transitModel, - Graph graph, - DataImportIssueStore issueStore, - BuildConfig config - ) { - this.transitModel = transitModel; - this.graph = graph; - this.issueStore = issueStore; - this.config = config; - } - - @Override - public void buildGraph() { - LOG.info("Start emissions building!"); - String url = config.emissions.getUrl(); - if (url != null && !url.isEmpty()) { - LOG.info("Fetching data from {}", url); - try { - var http = new OtpHttpClient(); - var data = http.getAndMapAsJsonNode(new URI(url), Map.of(), new ObjectMapper()); - if (data == null) { - throw new IOException("Did not find any emissions data from url " + url); - } - - Gson gson = new Gson(); - EmissionsService emissionsService; - if (config.emissions.getConfigName().equals("digitransitEmissions")) { - DigitransitEmissions[] digitransitEmissions = gson.fromJson( - String.valueOf(data), - DigitransitEmissions[].class - ); - emissionsService = new DigitransitEmissionsService(digitransitEmissions); - transitModel.setEmissionsService(emissionsService); - graph.setEmissionsService(new DigitransitEmissionsService(digitransitEmissions)); - } - } catch (Exception e) { - LOG.error("ERROR " + e); - } - } - } - - @Override - public void checkInputs() {} -} diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 6399a22d9e4..21167a05255 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -30,7 +30,6 @@ public enum OTPFeature { DebugClient(true, false, "Enable the debug web client located at the root of the web server."), FloatingBike(true, false, "Enable floating bike routing."), GtfsGraphQlApi(true, true, "Enable GTFS GraphQL API."), - co2Emissions(false, true, "Enable emissions calculation and data handling."), /** * If this feature flag is switched on, then the minimum transfer time is not the minimum transfer * time, but the definitive transfer time. Use this to override what we think the transfer will @@ -68,7 +67,7 @@ public enum OTPFeature { false, "Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads." ), - + Co2Emissions(false, true, "Enable emissions calculation and data handling."), DataOverlay( false, true, diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index 138720561c4..7eba070a7c5 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.framework.lang.OtpNumberFormat; @@ -60,6 +61,7 @@ public static GraphBuilder create( Graph graph, TransitModel transitModel, WorldEnvelopeRepository worldEnvelopeRepository, + EmissionsServiceRepository emissionsServiceRepository, boolean loadStreetGraph, boolean saveStreetGraph ) { @@ -79,6 +81,7 @@ public static GraphBuilder create( .worldEnvelopeRepository(worldEnvelopeRepository) .dataSources(dataSources) .timeZoneId(transitModel.getTimeZone()) + .emissionsServiceRepository(emissionsServiceRepository) .build(); var graphBuilder = factory.graphBuilder(); @@ -156,7 +159,7 @@ public static GraphBuilder create( graphBuilder.addModuleOptional(factory.dataOverlayFactory()); } - if (OTPFeature.co2Emissions.isOn()) { + if (OTPFeature.Co2Emissions.isOn()) { graphBuilder.addModule(factory.emissionsModule()); } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java index bc5198db932..4c6a3fa01e1 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java @@ -6,8 +6,9 @@ import java.time.ZoneId; import java.util.List; import javax.annotation.Nullable; -import org.opentripplanner.emissions.EmissionsModule; import org.opentripplanner.ext.dataoverlay.EdgeUpdaterModule; +import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsModule; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.ext.flex.AreaStopsToVerticesMapper; import org.opentripplanner.ext.transferanalyzer.DirectTransferAnalyzer; import org.opentripplanner.graph_builder.GraphBuilder; @@ -38,9 +39,7 @@ public interface GraphBuilderFactory { GraphBuilder graphBuilder(); OsmModule osmModule(); GtfsModule gtfsModule(); - - EmissionsModule emissionsModule(); - + DigitransitEmissionsModule emissionsModule(); NetexModule netexModule(); TimeZoneAdjusterModule timeZoneAdjusterModule(); TripPatternNamer tripPatternNamer(); @@ -78,5 +77,8 @@ interface Builder { Builder timeZoneId(@Nullable ZoneId zoneId); GraphBuilderFactory build(); + + @BindsInstance + Builder emissionsServiceRepository(EmissionsServiceRepository emissionsServiceRepository); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 2fcf28809a3..8651de80b9b 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -9,9 +9,10 @@ import java.util.ArrayList; import java.util.List; import org.opentripplanner.datastore.api.DataSource; -import org.opentripplanner.emissions.EmissionsModule; import org.opentripplanner.ext.dataoverlay.EdgeUpdaterModule; import org.opentripplanner.ext.dataoverlay.configure.DataOverlayFactory; +import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsModule; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.ext.transferanalyzer.DirectTransferAnalyzer; import org.opentripplanner.graph_builder.ConfiguredDataSource; import org.opentripplanner.graph_builder.GraphBuilderDataSources; @@ -111,14 +112,15 @@ static GtfsModule provideGtfsModule( @Provides @Singleton - static EmissionsModule provideEmissionsModule( + static DigitransitEmissionsModule provideEmissionsModule( GraphBuilderDataSources dataSources, BuildConfig config, Graph graph, TransitModel transitModel, - DataImportIssueStore issueStore + DataImportIssueStore issueStore, + EmissionsServiceRepository emissionsServiceRepository ) { - return new EmissionsModule(transitModel, graph, issueStore, config); + return new DigitransitEmissionsModule(config, emissionsServiceRepository); } @Provides diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index 7c125e2d313..a6b816b904c 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -51,7 +51,7 @@ public class Itinerary { /* Sandbox experimental properties */ private Float accessibilityScore; - private float emissions; + private Float emissions; /* other properties */ @@ -592,11 +592,12 @@ public List getScheduledTransitLegs() { /** * The co2 emissions of this itinerary. */ - public void setEmissions(float emissions) { + public void setEmissions(Float emissions) { this.emissions = emissions; } - public float getEmissions() { + @Nullable + public Float getEmissions() { return this.emissions; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index 70ec55f6bee..28364963a03 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -9,8 +9,6 @@ import java.util.function.Consumer; import java.util.function.Function; import org.opentripplanner.ext.accessibilityscore.AccessibilityScoreFilter; -import org.opentripplanner.ext.emissions.EmissionsFilter; -import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.fares.FaresFilter; import org.opentripplanner.framework.lang.Sandbox; import org.opentripplanner.model.plan.Itinerary; @@ -76,7 +74,7 @@ public class ItineraryListFilterChainBuilder { private double minBikeParkingDistance; @Sandbox - private EmissionsService emissionsService; + private ItineraryListFilter emissionsFilter; @Sandbox private ItineraryListFilter rideHailingFilter; @@ -274,8 +272,8 @@ public ItineraryListFilterChainBuilder withFares(FareService fareService) { return this; } - public ItineraryListFilterChainBuilder withEmissions(EmissionsService emissionsService) { - this.emissionsService = emissionsService; + public ItineraryListFilterChainBuilder withEmissions(ItineraryListFilter emissionsFilter) { + this.emissionsFilter = emissionsFilter; return this; } @@ -325,8 +323,8 @@ public ItineraryListFilterChain build() { filters.add(new FaresFilter(faresService)); } - if (emissionsService != null) { - filters.add(new EmissionsFilter(emissionsService)); + if (this.emissionsFilter != null) { + filters.add(this.emissionsFilter); } if (transitAlertService != null) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 857ba40d68d..124ea720a4e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -3,7 +3,9 @@ import java.time.Instant; import java.util.List; import java.util.function.Consumer; +import org.opentripplanner.ext.digitransitemissions.EmissionsFilter; import org.opentripplanner.ext.ridehailing.RideHailingFilter; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.algorithm.filterchain.GroupBySimilarity; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain; @@ -66,7 +68,6 @@ public static ItineraryListFilterChain createFilterChain( request.preferences().wheelchair().maxSlope() ) .withFares(context.graph().getFareService()) - .withEmissions(context.graph().getEmissionsService()) .withMinBikeParkingDistance(minBikeParkingDistance(request)) .withRemoveTimeshiftedItinerariesWithSameRoutesAndStops( params.removeItinerariesWithSameRoutesAndStops() @@ -87,6 +88,10 @@ public static ItineraryListFilterChain createFilterChain( ); } + if (OTPFeature.Co2Emissions.isOn() && context.emissionsService() != null) { + builder.withEmissions(new EmissionsFilter(context.emissionsService())); + } + return builder.build(); } diff --git a/src/main/java/org/opentripplanner/routing/graph/Graph.java b/src/main/java/org/opentripplanner/routing/graph/Graph.java index b8a121bdb0c..a8f4bc340fc 100644 --- a/src/main/java/org/opentripplanner/routing/graph/Graph.java +++ b/src/main/java/org/opentripplanner/routing/graph/Graph.java @@ -16,7 +16,6 @@ import javax.annotation.Nullable; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings; -import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.framework.geometry.CompactElevationProfile; import org.opentripplanner.framework.geometry.GeometryUtils; @@ -114,7 +113,6 @@ public class Graph implements Serializable { */ public DataOverlayParameterBindings dataOverlayParameterBindings; private LuceneIndex luceneIndex; - private EmissionsService emissionsService; @Inject public Graph( @@ -363,14 +361,6 @@ public void setFareService(FareService fareService) { this.fareService = fareService; } - public EmissionsService getEmissionsService() { - return emissionsService; - } - - public void setEmissionsService(EmissionsService emissionsService) { - this.emissionsService = emissionsService; - } - public LuceneIndex getLuceneIndex() { return luceneIndex; } diff --git a/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java b/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java index 6541c082f33..49ca0d0ae30 100644 --- a/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java +++ b/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java @@ -17,6 +17,7 @@ import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.datastore.api.DataSource; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.framework.geometry.CompactElevationProfile; import org.opentripplanner.framework.lang.OtpNumberFormat; @@ -75,6 +76,7 @@ public class SerializedGraphObject implements Serializable { public final DataImportIssueSummary issueSummary; private final int stopLocationCounter; private final int routingTripPatternCounter; + public final EmissionsServiceRepository emissionsServiceRepository; public SerializedGraphObject( Graph graph, @@ -82,7 +84,8 @@ public SerializedGraphObject( WorldEnvelopeRepository worldEnvelopeRepository, BuildConfig buildConfig, RouterConfig routerConfig, - DataImportIssueSummary issueSummary + DataImportIssueSummary issueSummary, + EmissionsServiceRepository emissionsServiceRepository ) { this.graph = graph; this.edges = graph.getEdges(); @@ -91,6 +94,7 @@ public SerializedGraphObject( this.buildConfig = buildConfig; this.routerConfig = routerConfig; this.issueSummary = issueSummary; + this.emissionsServiceRepository = emissionsServiceRepository; this.allTransitSubModes = SubMode.listAllCachedSubModes(); this.stopLocationCounter = StopLocation.indexCounter(); this.routingTripPatternCounter = RoutingTripPattern.indexCounter(); diff --git a/src/main/java/org/opentripplanner/standalone/OTPMain.java b/src/main/java/org/opentripplanner/standalone/OTPMain.java index 0d868e43eec..018388acdd0 100644 --- a/src/main/java/org/opentripplanner/standalone/OTPMain.java +++ b/src/main/java/org/opentripplanner/standalone/OTPMain.java @@ -151,7 +151,8 @@ private static void startOTPServer(CommandLineParameters cli) { app.worldEnvelopeRepository(), config.buildConfig(), config.routerConfig(), - DataImportIssueSummary.combine(graphBuilder.issueSummary(), app.dataImportIssueSummary()) + DataImportIssueSummary.combine(graphBuilder.issueSummary(), app.dataImportIssueSummary()), + app.emissionsServiceRepository() ) .save(app.graphOutputDataSource()); // Log size info for the deduplicator diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index a7effd16704..b853e4de583 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -5,6 +5,7 @@ import java.util.Locale; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext; +import org.opentripplanner.ext.digitransitemissions.EmissionsService; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.ext.vectortiles.VectorTilesResource; import org.opentripplanner.framework.application.OTPFeature; @@ -94,6 +95,8 @@ public interface OtpServerRequestContext { MeterRegistry meterRegistry(); + EmissionsService emissionsService(); + /** Inspector/debug services */ TileRendererManager tileRendererManager(); diff --git a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index b917365c607..faa31e9b34c 100644 --- a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -17,8 +17,8 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; import org.opentripplanner.datastore.api.OtpDataStoreConfig; -import org.opentripplanner.emissions.EmissionsConfig; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayConfig; +import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsConfig; import org.opentripplanner.ext.fares.FaresConfiguration; import org.opentripplanner.framework.geometry.CompactElevationProfile; import org.opentripplanner.framework.lang.ObjectUtils; @@ -167,7 +167,7 @@ public class BuildConfig implements OtpDataStoreConfig { public final Set boardingLocationTags; public final DemExtractParametersList dem; public final OsmExtractParametersList osm; - public final EmissionsConfig emissions; + public final DigitransitEmissionsConfig digitransitEmissions; public final TransitFeeds transitFeeds; public boolean staticParkAndRide; public boolean staticBikeParkAndRide; @@ -627,7 +627,7 @@ that we support remote input files (cloud storage or arbitrary URLs) not all dat osm = OsmConfig.mapOsmConfig(root, "osm", osmDefaults); demDefaults = DemConfig.mapDemDefaultsConfig(root, "demDefaults"); dem = DemConfig.mapDemConfig(root, "dem", demDefaults); - emissions = new EmissionsConfig("digitransitEmissions", root); + digitransitEmissions = new DigitransitEmissionsConfig("digitransitEmissions", root); netexDefaults = NetexConfig.mapNetexDefaultParameters(root, "netexDefaults"); gtfsDefaults = GtfsConfig.mapGtfsDefaultParameters(root, "gtfsDefaults"); diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index ee71aa3f542..ed184b92559 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -3,6 +3,7 @@ import jakarta.ws.rs.core.Application; import javax.annotation.Nullable; import org.opentripplanner.datastore.api.DataSource; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.transmodelapi.TransmodelAPI; import org.opentripplanner.framework.application.LogMDCSupport; @@ -70,7 +71,8 @@ public class ConstructApplication { WorldEnvelopeRepository worldEnvelopeRepository, ConfigModel config, GraphBuilderDataSources graphBuilderDataSources, - DataImportIssueSummary issueSummary + DataImportIssueSummary issueSummary, + EmissionsServiceRepository emissionsServiceRepository ) { this.cli = cli; this.graphBuilderDataSources = graphBuilderDataSources; @@ -87,6 +89,7 @@ public class ConstructApplication { .transitModel(transitModel) .graphVisualizer(graphVisualizer) .worldEnvelopeRepository(worldEnvelopeRepository) + .emissionsServiceRepository(emissionsServiceRepository) .dataImportIssueSummary(issueSummary) .build(); } @@ -118,6 +121,7 @@ public GraphBuilder createGraphBuilder() { graph(), transitModel(), factory.worldEnvelopeRepository(), + factory.emissionsServiceRepository(), cli.doLoadStreetGraph(), cli.doSaveStreetGraph() ); @@ -288,4 +292,8 @@ private void enableRequestTraceLogging() { private void createMetricsLogging() { factory.metricsLogging(); } + + public EmissionsServiceRepository emissionsServiceRepository() { + return factory.emissionsServiceRepository(); + } } diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index f882bad3ffd..500c84236c5 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -4,6 +4,9 @@ import dagger.Component; import jakarta.inject.Singleton; import javax.annotation.Nullable; +import org.opentripplanner.ext.digitransitemissions.EmissionsService; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceModule; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.ext.ridehailing.configure.RideHailingServicesModule; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.raptor.configure.RaptorConfig; @@ -44,6 +47,7 @@ VehicleRentalRepositoryModule.class, ConstructApplicationModule.class, RideHailingServicesModule.class, + EmissionsServiceModule.class, } ) public interface ConstructApplicationFactory { @@ -67,6 +71,9 @@ public interface ConstructApplicationFactory { MetricsLogging metricsLogging(); + EmissionsServiceRepository emissionsServiceRepository(); + EmissionsService emissionsService(); + @Component.Builder interface Builder { @BindsInstance @@ -87,6 +94,9 @@ interface Builder { @BindsInstance Builder dataImportIssueSummary(DataImportIssueSummary issueSummary); + @BindsInstance + Builder emissionsServiceRepository(EmissionsServiceRepository emissionsServiceRepository); + ConstructApplicationFactory build(); } } diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index bc677028296..d2b767e1f7a 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -6,6 +6,7 @@ import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.astar.spi.TraverseVisitor; +import org.opentripplanner.ext.digitransitemissions.EmissionsService; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; @@ -32,7 +33,8 @@ OtpServerRequestContext providesServerContext( VehiclePositionService vehiclePositionService, VehicleRentalService vehicleRentalService, List rideHailingServices, - @Nullable TraverseVisitor traverseVisitor + @Nullable TraverseVisitor traverseVisitor, + EmissionsService emissionsService ) { return DefaultServerRequestContext.create( routerConfig.transitTuningConfig(), @@ -45,6 +47,7 @@ OtpServerRequestContext providesServerContext( worldEnvelopeService, vehiclePositionService, vehicleRentalService, + emissionsService, routerConfig.flexConfig(), rideHailingServices, traverseVisitor diff --git a/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java b/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java index 9b29d8827e8..6fae77f94de 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java @@ -1,6 +1,7 @@ package org.opentripplanner.standalone.configure; import org.opentripplanner.datastore.api.DataSource; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.graph_builder.GraphBuilderDataSources; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.routing.graph.Graph; @@ -52,7 +53,8 @@ public ConstructApplication appConstruction(SerializedGraphObject obj) { obj.graph, obj.transitModel, obj.worldEnvelopeRepository, - obj.issueSummary + obj.issueSummary, + obj.emissionsServiceRepository ); } @@ -62,7 +64,8 @@ public ConstructApplication appConstruction() { factory.emptyGraph(), factory.emptyTransitModel(), factory.emptyWorldEnvelopeRepository(), - DataImportIssueSummary.empty() + DataImportIssueSummary.empty(), + factory.emptyEmissionsServiceRepository() ); } @@ -81,7 +84,8 @@ private ConstructApplication createAppConstruction( Graph graph, TransitModel transitModel, WorldEnvelopeRepository worldEnvelopeRepository, - DataImportIssueSummary issueSummary + DataImportIssueSummary issueSummary, + EmissionsServiceRepository emissionsServiceRepository ) { return new ConstructApplication( cli, @@ -90,7 +94,8 @@ private ConstructApplication createAppConstruction( worldEnvelopeRepository, config(), graphBuilderDataSources(), - issueSummary + issueSummary, + emissionsServiceRepository ); } } diff --git a/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java index 5839f4f5b69..6230c8c1fd6 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java @@ -6,6 +6,8 @@ import org.opentripplanner.datastore.OtpDataStore; import org.opentripplanner.datastore.configure.DataStoreModule; import org.opentripplanner.ext.datastore.gs.GsDataSourceModule; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepositoryModule; import org.opentripplanner.graph_builder.GraphBuilderDataSources; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; @@ -25,6 +27,7 @@ DataStoreModule.class, GsDataSourceModule.class, WorldEnvelopeRepositoryModule.class, + EmissionsServiceRepositoryModule.class, } ) public interface LoadApplicationFactory { @@ -44,6 +47,9 @@ public interface LoadApplicationFactory { @Singleton GraphBuilderDataSources graphBuilderDataSources(); + @Singleton + EmissionsServiceRepository emptyEmissionsServiceRepository(); + @Component.Builder interface Builder { @BindsInstance diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index dad75d1f6f6..8ae6de8dc0c 100644 --- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -5,6 +5,7 @@ import java.util.Locale; import javax.annotation.Nullable; import org.opentripplanner.astar.spi.TraverseVisitor; +import org.opentripplanner.ext.digitransitemissions.EmissionsService; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.ext.vectortiles.VectorTilesResource; import org.opentripplanner.inspector.raster.TileRendererManager; @@ -43,6 +44,7 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { private final WorldEnvelopeService worldEnvelopeService; private final VehiclePositionService vehiclePositionService; private final VehicleRentalService vehicleRentalService; + private final EmissionsService emissionsService; /** * Make sure all mutable components are copied/cloned before calling this constructor. @@ -59,6 +61,7 @@ private DefaultServerRequestContext( WorldEnvelopeService worldEnvelopeService, VehiclePositionService vehiclePositionService, VehicleRentalService vehicleRentalService, + EmissionsService emissionsService, List rideHailingServices, TraverseVisitor traverseVisitor, FlexConfig flexConfig @@ -77,6 +80,7 @@ private DefaultServerRequestContext( this.worldEnvelopeService = worldEnvelopeService; this.vehiclePositionService = vehiclePositionService; this.rideHailingServices = rideHailingServices; + this.emissionsService = emissionsService; } /** @@ -93,6 +97,7 @@ public static DefaultServerRequestContext create( WorldEnvelopeService worldEnvelopeService, VehiclePositionService vehiclePositionService, VehicleRentalService vehicleRentalService, + @Nullable EmissionsService emissionsService, FlexConfig flexConfig, List rideHailingServices, @Nullable TraverseVisitor traverseVisitor @@ -109,6 +114,7 @@ public static DefaultServerRequestContext create( worldEnvelopeService, vehiclePositionService, vehicleRentalService, + emissionsService, rideHailingServices, traverseVisitor, flexConfig @@ -206,4 +212,9 @@ public FlexConfig flexConfig() { public VectorTilesResource.LayersParameters vectorTileLayers() { return vectorTileLayers; } + + @Override + public EmissionsService emissionsService() { + return emissionsService; + } } diff --git a/src/main/java/org/opentripplanner/transit/service/TransitModel.java b/src/main/java/org/opentripplanner/transit/service/TransitModel.java index 351324d9aa4..e845aa27dfb 100644 --- a/src/main/java/org/opentripplanner/transit/service/TransitModel.java +++ b/src/main/java/org/opentripplanner/transit/service/TransitModel.java @@ -21,7 +21,6 @@ import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.flex.trip.FlexTrip; import org.opentripplanner.framework.lang.ObjectUtils; import org.opentripplanner.framework.time.ServiceDateUtils; @@ -108,8 +107,6 @@ public class TransitModel implements Serializable { private transient TransitAlertService transitAlertService; - private EmissionsService emissionsService; - @Inject public TransitModel(StopModel stopModel, Deduplicator deduplicator) { this.stopModel = Objects.requireNonNull(stopModel); @@ -579,12 +576,4 @@ private void initTimeZone() { } } } - - public EmissionsService getEmissionsService() { - return emissionsService; - } - - public void setEmissionsService(EmissionsService emissionsService) { - this.emissionsService = emissionsService; - } } diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index 969769f93fa..14212dd369f 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -4,6 +4,9 @@ import io.micrometer.core.instrument.Metrics; import java.util.List; +import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsService; +import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsService; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.vehiclepositions.VehiclePositionService; @@ -41,6 +44,7 @@ public static OtpServerRequestContext createServerContext( createWorldEnvelopeService(), createVehiclePositionService(), createVehicleRentalService(), + createEmissionsService(), routerConfig.flexConfig(), List.of(), null @@ -61,4 +65,8 @@ public static VehiclePositionService createVehiclePositionService() { public static VehicleRentalService createVehicleRentalService() { return new DefaultVehicleRentalService(); } + + public static EmissionsService createEmissionsService() { + return new DefaultEmissionsService(new DefaultEmissionsServiceRepository()); + } } diff --git a/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java b/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java index bcb5eb3a627..9b707f7202c 100644 --- a/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java +++ b/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java @@ -19,6 +19,8 @@ import org.opentripplanner.TestOtpModel; import org.opentripplanner.datastore.api.FileType; import org.opentripplanner.datastore.file.FileDataSource; +import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.framework.geometry.HashGridSpatialIndex; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; @@ -61,7 +63,8 @@ public class GraphSerializationTest { public void testRoundTripSerializationForGTFSGraph() throws Exception { TestOtpModel model = ConstantsForTests.buildNewPortlandGraph(true); var weRepo = new DefaultWorldEnvelopeRepository(); - testRoundTrip(model.graph(), model.transitModel(), weRepo); + var emissionsRepo = new DefaultEmissionsServiceRepository(); + testRoundTrip(model.graph(), model.transitModel(), weRepo, emissionsRepo); } /** @@ -71,7 +74,8 @@ public void testRoundTripSerializationForGTFSGraph() throws Exception { public void testRoundTripSerializationForNetexGraph() throws Exception { TestOtpModel model = ConstantsForTests.buildNewMinimalNetexGraph(); var worldEnvelopeRepository = new DefaultWorldEnvelopeRepository(); - testRoundTrip(model.graph(), model.transitModel(), worldEnvelopeRepository); + var emissionsRepo = new DefaultEmissionsServiceRepository(); + testRoundTrip(model.graph(), model.transitModel(), worldEnvelopeRepository, emissionsRepo); } // Ideally we'd also test comparing two separate but identical complex graphs, built separately from the same inputs. @@ -169,7 +173,8 @@ private static void assertNoDifferences(Graph g1, Graph g2) { private void testRoundTrip( Graph originalGraph, TransitModel originalTransitModel, - WorldEnvelopeRepository worldEnvelopeRepository + WorldEnvelopeRepository worldEnvelopeRepository, + EmissionsServiceRepository emissionsServiceRepository ) throws Exception { // Now round-trip the graph through serialization. File tempFile = TempFile.createTempFile("graph", "pdx"); @@ -179,7 +184,8 @@ private void testRoundTrip( worldEnvelopeRepository, BuildConfig.DEFAULT, RouterConfig.DEFAULT, - DataImportIssueSummary.empty() + DataImportIssueSummary.empty(), + emissionsServiceRepository ); serializedObj.save(new FileDataSource(tempFile, FileType.GRAPH)); SerializedGraphObject deserializedGraph = SerializedGraphObject.load(tempFile); diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index 5faffc6a606..46d6d431e76 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -15,6 +15,8 @@ import java.util.function.Predicate; import org.opentripplanner.TestServerContext; import org.opentripplanner.datastore.OtpDataStore; +import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.configure.RaptorConfig; @@ -113,6 +115,7 @@ public SpeedTest( TestServerContext.createWorldEnvelopeService(), TestServerContext.createVehiclePositionService(), TestServerContext.createVehicleRentalService(), + null, // new DigitransitEmissionsService(new DefaultEmissionsServiceRepository()), config.flexConfig, List.of(), null From 7207521296b2af1e200e2b5344197130e05e8d66 Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 4 Sep 2023 09:42:52 +0300 Subject: [PATCH 12/68] test fixed --- .../ext/emissions/digitransit/EmissionsServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index a859eaa76ba..8f2aa6c299d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -63,7 +63,7 @@ void getEmissionsForItinerary() { var stoptimes = new ArrayList(); stoptimes.add(stoptime); var trip = Trip - .of(FeedScopedId.parseId("FOO:BAR")) + .of(FeedScopedId.parse("FOO:BAR")) .withMode(TransitMode.BUS) .withRoute(route) .build(); @@ -98,7 +98,7 @@ void getEmissionsForCarRoute() { var stoptimes = new ArrayList(); stoptimes.add(stoptime); var trip = Trip - .of(FeedScopedId.parseId("F:A")) + .of(FeedScopedId.parse("F:A")) .withMode(TransitMode.BUS) .withRoute(route) .build(); From ab6efe8f7340a5373c92eddabe14caadd756603f Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 4 Sep 2023 11:17:14 +0300 Subject: [PATCH 13/68] emissions added to datafetchers --- .../ext/digitransitemissions/DefaultEmissionsService.java | 1 - .../ext/gtfsgraphqlapi/datafetchers/ItineraryImpl.java | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java index 2e283fcbcba..3bbfba935cb 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java @@ -3,7 +3,6 @@ import jakarta.inject.Inject; import java.io.Serializable; import java.util.Optional; -import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.model.plan.Itinerary; public class DefaultEmissionsService implements Serializable, EmissionsService { diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/ItineraryImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/ItineraryImpl.java index 5ea0ff8f895..c02fba63144 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/ItineraryImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/ItineraryImpl.java @@ -103,4 +103,9 @@ public DataFetcher accessibilityScore() { private Itinerary getSource(DataFetchingEnvironment environment) { return environment.getSource(); } + + @Override + public DataFetcher emissions() { + return environment -> NumberMapper.toDouble(getSource(environment).getEmissions()); + } } From 571757e7b4bb54cf2c332ddac9ef51531014ce4a Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 14 Sep 2023 13:11:38 +0300 Subject: [PATCH 14/68] emissions data from gtfs.zip --- .../digitransit/EmissionsServiceTest.java | 2 +- .../DigitransitEmissions.java | 3 + .../DigitransitEmissionsConfig.java | 21 +++- .../DigitransitEmissionsModule.java | 101 +++++++++++------- .../DigitransitEmissionsService.java | 46 ++++---- .../generated/GraphQLDataFetchers.java | 21 +++- .../generated/GraphQLTypes.java | 1 + .../module/configure/GraphBuilderModules.java | 5 +- 8 files changed, 128 insertions(+), 72 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index 8f2aa6c299d..b87faf2ff02 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -48,7 +48,7 @@ void SetUp() { Map digitransitEmissions = new HashMap<>(); digitransitEmissions.put("F:F:1:BUS", new DigitransitEmissions(120, 12)); digitransitEmissions.put("CAR", new DigitransitEmissions(1100, 1)); - this.eService = new DigitransitEmissionsService(digitransitEmissions); + this.eService = new DigitransitEmissionsService(digitransitEmissions, 131); } @Test diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java index e5f32b2ee14..e1fe704f663 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java @@ -9,6 +9,9 @@ public record DigitransitEmissions(double avg, int passengerAvg) { } public double getEmissionsPerPassenger() { + if (this.passengerAvg <= 1) { + return this.avg; + } return this.avg / this.passengerAvg; } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java index 07f733e8210..6f4b71dd81f 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java @@ -9,7 +9,8 @@ */ public class DigitransitEmissionsConfig { - private String url; + private int carAvgCo2; + private double carAvgOccupancy; public DigitransitEmissionsConfig(String parameterName, NodeAdapter root) { var c = root @@ -25,10 +26,22 @@ By specifying a URL to fetch emissions data, the program gains access to carbon ) .asObject(); - this.url = c.of("url").since(V2_4).summary("Url to emissions json file.").asString(""); + this.carAvgCo2 = + c.of("carAvgCo2").since(V2_4).summary("The average CO2 emissions of a car.").asInt(1); + + this.carAvgOccupancy = + c + .of("carAvgOccupancy") + .since(V2_4) + .summary("The average number of passengers in a car.") + .asDouble(1.1); + } + + public int getCarAvgCo2() { + return carAvgCo2; } - public String getUrl() { - return url; + public double getCarAvgOccupancy() { + return carAvgOccupancy; } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java index e99d204e910..1b12acda218 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java @@ -1,17 +1,21 @@ package org.opentripplanner.ext.digitransitemissions; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.csvreader.CsvReader; import dagger.Module; import jakarta.inject.Inject; +import java.io.File; import java.io.IOException; -import java.net.URI; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -import org.opentripplanner.framework.io.OtpHttpClient; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.opentripplanner.graph_builder.ConfiguredDataSource; +import org.opentripplanner.graph_builder.GraphBuilderDataSources; import org.opentripplanner.graph_builder.model.GraphBuilderModule; +import org.opentripplanner.gtfs.graphbuilder.GtfsFeedParameters; import org.opentripplanner.standalone.config.BuildConfig; -import org.opentripplanner.street.search.TraverseMode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,12 +25,15 @@ public class DigitransitEmissionsModule implements GraphBuilderModule { private static final Logger LOG = LoggerFactory.getLogger(DigitransitEmissionsModule.class); private BuildConfig config; private EmissionsServiceRepository emissionsServiceRepository; + private GraphBuilderDataSources dataSources; @Inject public DigitransitEmissionsModule( + GraphBuilderDataSources dataSources, BuildConfig config, EmissionsServiceRepository emissionsServiceRepository ) { + this.dataSources = dataSources; this.config = config; this.emissionsServiceRepository = emissionsServiceRepository; } @@ -34,48 +41,64 @@ public DigitransitEmissionsModule( public void buildGraph() { if (config.digitransitEmissions != null) { LOG.info("Start emissions building!"); - String url = config.digitransitEmissions.getUrl(); - if (url != null && !url.isEmpty()) { - LOG.info("Fetching data from {}", url); - try { - var http = new OtpHttpClient(); - JsonNode data = http.getAndMapAsJsonNode(new URI(url), Map.of(), new ObjectMapper()); - if (data == null) { - throw new IOException("Did not find any emissions data from url " + url); - } - Map digitransitEmissions = parseDigitransitEmissions(data); - this.emissionsServiceRepository.saveEmissionsService( - new DigitransitEmissionsService(digitransitEmissions) - ); - } catch (Exception e) { - LOG.error("ERROR ", e); + int carAvgCo2 = config.digitransitEmissions.getCarAvgCo2(); + double carAvgOccupancy = config.digitransitEmissions.getCarAvgOccupancy(); + double carAvgEmissions = carAvgCo2 / carAvgOccupancy; + + this.emissionsServiceRepository.saveEmissionsService( + new DigitransitEmissionsService(readGtfs(), carAvgEmissions) + ); + } + } + + private Map readGtfs() { + Map emissionsData = new HashMap<>(); + + try { + for (ConfiguredDataSource gtfsData : dataSources.getGtfsConfiguredDatasource()) { + ZipFile zipFile = new ZipFile(new File(gtfsData.dataSource().path()), ZipFile.OPEN_READ); + String feedId = readFeedId(zipFile); + ZipEntry entry = zipFile.getEntry("emissions.txt"); + InputStream stream = zipFile.getInputStream(entry); + CsvReader reader = new CsvReader(stream, ';', StandardCharsets.UTF_8); + reader.readHeaders(); + while (reader.readRecord()) { + emissionsData.put(getKey(reader, feedId), getEmissions(reader)); } + zipFile.close(); } + } catch (IOException e) { + LOG.error("Reading emissions data failed.", e); } + return emissionsData; } - private Map parseDigitransitEmissions(JsonNode data) { - Map digitransitEmissions = new HashMap<>(); - for (JsonNode line : data) { - digitransitEmissions.put( - getKey(line), - new DigitransitEmissions(line.get("avg").asDouble(), line.get("p_avg").intValue()) - ); + private String readFeedId(ZipFile zipFile) { + try { + ZipEntry feedInfoEntry = zipFile.getEntry("feed_info.txt"); + InputStream stream = zipFile.getInputStream(feedInfoEntry); + CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); + reader.readHeaders(); + reader.readRecord(); + return reader.get("feed_id"); + } catch (IOException e) { + LOG.error("Reading feed id for emissions failed.", e); + throw new RuntimeException(e); } - return digitransitEmissions; } - private String getKey(JsonNode data) { - if (data.get("mode").asText().equals(TraverseMode.CAR.toString())) { - return TraverseMode.CAR.toString(); - } - String key = - data.get("db").asText() + - ":" + - data.get("agency_id").asText() + - ":" + - data.get("mode").asText(); - return key; + private String getKey(CsvReader reader, String feedId) throws IOException { + String routeId = reader.get("route_id"); + String agencyId = reader.get("agency_id"); + String routeShortName = reader.get("route_short_name"); + String type = reader.get("type"); + return feedId + ":" + agencyId + ":" + routeId + ":" + routeShortName + ":" + type; + } + + private DigitransitEmissions getEmissions(CsvReader reader) throws IOException { + Double avg = Double.parseDouble(reader.get("avg")); + int pAvg = Integer.parseInt(reader.get("p_avg")); + return new DigitransitEmissions(avg, pAvg); } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java index 4560147a4e1..0886d7921a4 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java @@ -18,9 +18,14 @@ public class DigitransitEmissionsService implements Serializable, EmissionsService { private Map emissions; + private double carAvgEmissions; - public DigitransitEmissionsService(Map emissions) { + public DigitransitEmissionsService( + Map emissions, + double carAvgEmissions + ) { this.emissions = emissions; + this.carAvgEmissions = carAvgEmissions; } @Override @@ -45,8 +50,7 @@ public Float getEmissionsForItinerary(Itinerary itinerary) { .toList(); if (!carLegs.isEmpty()) { - float emis = (float) getEmissionsForCarItinerary(carLegs); - return emis; + return (float) getEmissionsForCarItinerary(carLegs); } return null; } @@ -58,7 +62,15 @@ private double getEmissionsForTransitItinerary(List transitLegs) { double legDistanceInKm = leg.getDistanceMeters() / 1000; FeedScopedId feedScopedAgencyId = leg.getAgency().getId(); String modeName = leg.getMode().name(); - String key = feedScopedAgencyId + ":" + modeName; + + String key = + feedScopedAgencyId + + ":" + + leg.getRoute().getId().getId() + + ":" + + leg.getRoute().getShortName() + + ":" + + modeName; if (key != null && this.emissions.containsKey(key)) { return this.emissions.get(key).getEmissionsPerPassenger() * legDistanceInKm; @@ -66,28 +78,20 @@ private double getEmissionsForTransitItinerary(List transitLegs) { return -1; }); DoubleSummaryStatistics stats = emissionsStream.summaryStatistics(); - if (stats.getMin() < 0) { + Double sum = stats.getSum(); + if (stats.getMin() < 0 || Double.isNaN(sum)) { return -1; } - Double sum = stats.getSum(); - return sum; } private double getEmissionsForCarItinerary(List carLegs) { - if (this.emissions.containsKey(TraverseMode.CAR.toString())) { - double emis = carLegs - .stream() - .mapToDouble(leg -> { - double carLegDistanceInKm = leg.getDistanceMeters() / 1000; - return ( - this.emissions.get(TraverseMode.CAR.toString()).getEmissionsPerPassenger() * - carLegDistanceInKm - ); - }) - .sum(); - return emis; - } - return -1; + return carLegs + .stream() + .mapToDouble(leg -> { + double carLegDistanceInKm = leg.getDistanceMeters() / 1000; + return (this.carAvgEmissions * carLegDistanceInKm); + }) + .sum(); } } diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java index 318c4fc2a11..482cf11b788 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java @@ -2,11 +2,14 @@ package org.opentripplanner.ext.gtfsgraphqlapi.generated; import graphql.relay.Connection; +import graphql.relay.Connection; +import graphql.relay.Edge; import graphql.relay.Edge; import graphql.schema.DataFetcher; import graphql.schema.TypeResolver; import java.util.Currency; import java.util.Map; +import java.util.Map; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.api.resource.DebugOutput; @@ -20,7 +23,11 @@ import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLRoutingErrorCode; import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLTransitMode; import org.opentripplanner.ext.gtfsgraphqlapi.model.RideHailingProvider; +import org.opentripplanner.ext.gtfsgraphqlapi.model.RouteTypeModel; +import org.opentripplanner.ext.gtfsgraphqlapi.model.StopOnRouteModel; +import org.opentripplanner.ext.gtfsgraphqlapi.model.StopOnTripModel; import org.opentripplanner.ext.gtfsgraphqlapi.model.StopPosition; +import org.opentripplanner.ext.gtfsgraphqlapi.model.UnknownModel; import org.opentripplanner.ext.ridehailing.model.RideEstimate; import org.opentripplanner.model.StopTimesInPattern; import org.opentripplanner.model.SystemNotice; @@ -41,6 +48,8 @@ import org.opentripplanner.routing.graphfinder.PatternAtStop; import org.opentripplanner.routing.graphfinder.PlaceAtDistance; import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; @@ -49,6 +58,7 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.network.Route; @@ -309,9 +319,12 @@ public interface GraphQLDefaultFareProduct { } /** - * Departure row is a location, which lists departures of a certain pattern from a - * stop. Departure rows are identified with the pattern, so querying departure rows - * will return only departures from one stop per pattern + * Departure row is a combination of a pattern and a stop of that pattern. + * + * They are de-duplicated so for each pattern there will only be a single departure row. + * + * This is useful if you want to show a list of stop/pattern combinations but want each pattern to be + * listed only once. */ public interface GraphQLDepartureRow { public DataFetcher id(); @@ -386,6 +399,8 @@ public interface GraphQLItinerary { public DataFetcher elevationLost(); + public DataFetcher emissions(); + public DataFetcher endTime(); public DataFetcher>> fares(); diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java index 3861f0e49ef..6780c978d08 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java @@ -1,6 +1,7 @@ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. package org.opentripplanner.ext.gtfsgraphqlapi.generated; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 8651de80b9b..1ec2804737b 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -115,12 +115,9 @@ static GtfsModule provideGtfsModule( static DigitransitEmissionsModule provideEmissionsModule( GraphBuilderDataSources dataSources, BuildConfig config, - Graph graph, - TransitModel transitModel, - DataImportIssueStore issueStore, EmissionsServiceRepository emissionsServiceRepository ) { - return new DigitransitEmissionsModule(config, emissionsServiceRepository); + return new DigitransitEmissionsModule(dataSources, config, emissionsServiceRepository); } @Provides From 7ec48e4b595edb8cefddfe724b137b95f3d0655d Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 14 Sep 2023 13:40:52 +0300 Subject: [PATCH 15/68] data should use comma separator --- .../digitransitemissions/DigitransitEmissionsModule.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java index 1b12acda218..8f1acac4242 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java @@ -59,9 +59,8 @@ private Map readGtfs() { for (ConfiguredDataSource gtfsData : dataSources.getGtfsConfiguredDatasource()) { ZipFile zipFile = new ZipFile(new File(gtfsData.dataSource().path()), ZipFile.OPEN_READ); String feedId = readFeedId(zipFile); - ZipEntry entry = zipFile.getEntry("emissions.txt"); - InputStream stream = zipFile.getInputStream(entry); - CsvReader reader = new CsvReader(stream, ';', StandardCharsets.UTF_8); + InputStream stream = zipFile.getInputStream(zipFile.getEntry("emissions.txt")); + CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); reader.readHeaders(); while (reader.readRecord()) { emissionsData.put(getKey(reader, feedId), getEmissions(reader)); @@ -76,8 +75,7 @@ private Map readGtfs() { private String readFeedId(ZipFile zipFile) { try { - ZipEntry feedInfoEntry = zipFile.getEntry("feed_info.txt"); - InputStream stream = zipFile.getInputStream(feedInfoEntry); + InputStream stream = zipFile.getInputStream(zipFile.getEntry("feed_info.txt")); CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); reader.readHeaders(); reader.readRecord(); From bd7285b4da4225f163dc44e05525fc879724aca5 Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 14 Sep 2023 15:32:36 +0300 Subject: [PATCH 16/68] documentations fixed, comment removed --- docs/sandbox/DigitransitEmissions.md | 41 +++++++++---------- .../DigitransitEmissionsConfig.java | 12 +----- .../transit/speed_test/SpeedTest.java | 3 +- .../standalone/config/build-config.json | 3 +- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md index 4563ad43bb2..6f0654f3d7c 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/DigitransitEmissions.md @@ -10,36 +10,33 @@ CO2 Emissions and ability to show them via plan queries in itinerary by Digitran This implementation enables OTP to retrieve CO2 information, which is then utilized during itinerary queries. The emissions are represented in grams per kilometer (g/Km) unit -Emissions data should be in json array with objects that have the following properties: +Emissions data is located in an emissions.txt file within a gtfs.zip and has the following properties: -`db`: database it refers to or CAR when referring to personal travel by car. For Example Linkki, HSL +`route_id`: route id -`agency_id`: agency id as appeared in GTFS or CAR when referring to personal travel by car. +`agency_id`: agency id -`agency_name`: Name of agency, or CAR when referring to personal travel by a car. +`route_short_name`: Short name of the route. -`mode`: mode of transportation it provides or CAR when referring to personal travel by car. +`type`: Mode of transportation. -`avg`: Carbon dioxide equivalent value for the `mode` at grams/Km units +`avg`: Average carbon dioxide equivalent value for the vehicles used on the route at grams/Km units -`p_avg`: Average passenger count for that mode of travel provided by that agency. +`p_avg`: Average passenger count for the vehicles on the route. For example: -```json -[ -{"db": "FOO", "agency_id": "FOO", "agency_name": "FOO_CO", "mode": "BUS", "avg": "88.17", "p_avg": 32}, -{"db": "CAR", "agency_id": "CAR", "agency_name": "CAR", "mode": "CAR", "avg": "123.7", "p_avg": 1} -] +```csv +route_id,agency_id,route_short_name,type,avg,p_avg +1234,HSL,545,BUS,123,20 +2345,HSL,1,TRAM,0,0 ``` -Emissions data is loaded from the provided source and embedded into the graph during the build process. +Emissions data is loaded from the gtfs.zip file(s) and embedded into the graph during the build process. ### Configuration To enable this functionality, you need to add the "Co2Emissions" feature in the -`otp-config.json` file. Include the `digitransitEmissions` object in the -`build-config.json` file. The `digitransitEmissions` object should contain a parameter called `url`, -which should be set to the location where the emissions data is stored. +`otp-config.json` file. ```json //otp-config.json @@ -47,21 +44,23 @@ which should be set to the location where the emissions data is stored. "Co2Emissions": true } ``` -include the `digitransitEmissions` object in the -`build-config.json` file. Object should contain `url` parameter, -which should be set to the location where the emissions data is stored. +Include the `digitransitEmissions` object in the +`build-config.json` file. The `digitransitEmissions` object should contain parameters called +`carAvgCo2` and `carAvgOccupancy`. The `carAvgCo2` provides the average emissions value for a car and +the `carAvgOccupancy` provides the average number of passengers in a car. ```json //build-config.json { "digitransitEmissions": { - "url": "https://your-url" + "carAvgCo2": 170, + "carAvgOccupancy": 1.3 } } ``` ## Changelog -### OTP 2.4 +### OTP 2.5 - Initial implementation of the emissions calculation. diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java index 6f4b71dd81f..34e16fe2347 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java @@ -1,7 +1,5 @@ package org.opentripplanner.ext.digitransitemissions; -import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_4; - import org.opentripplanner.standalone.config.framework.json.NodeAdapter; /** @@ -15,7 +13,6 @@ public class DigitransitEmissionsConfig { public DigitransitEmissionsConfig(String parameterName, NodeAdapter root) { var c = root .of(parameterName) - .since(V2_4) .summary("Configure properties for emissions file.") .description( """ @@ -26,15 +23,10 @@ By specifying a URL to fetch emissions data, the program gains access to carbon ) .asObject(); - this.carAvgCo2 = - c.of("carAvgCo2").since(V2_4).summary("The average CO2 emissions of a car.").asInt(1); + this.carAvgCo2 = c.of("carAvgCo2").summary("The average CO2 emissions of a car.").asInt(170); this.carAvgOccupancy = - c - .of("carAvgOccupancy") - .since(V2_4) - .summary("The average number of passengers in a car.") - .asDouble(1.1); + c.of("carAvgOccupancy").summary("The average number of passengers in a car.").asDouble(1.3); } public int getCarAvgCo2() { diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index 46d6d431e76..6dae1ef4153 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -17,6 +17,7 @@ import org.opentripplanner.datastore.OtpDataStore; import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsServiceRepository; import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; +import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.configure.RaptorConfig; @@ -115,7 +116,7 @@ public SpeedTest( TestServerContext.createWorldEnvelopeService(), TestServerContext.createVehiclePositionService(), TestServerContext.createVehicleRentalService(), - null, // new DigitransitEmissionsService(new DefaultEmissionsServiceRepository()), + null, config.flexConfig, List.of(), null diff --git a/src/test/resources/standalone/config/build-config.json b/src/test/resources/standalone/config/build-config.json index 140b0231c08..47402d67bec 100644 --- a/src/test/resources/standalone/config/build-config.json +++ b/src/test/resources/standalone/config/build-config.json @@ -75,6 +75,7 @@ } ], "digitransitEmissions": { - "url": "foo.bar" + "carAvgCo2": 170, + "carAvgOccupancy": 1.3 } } From ccecb7ed11ef94194413d01ef6cdbd206b7e7033 Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 14 Sep 2023 17:44:23 +0300 Subject: [PATCH 17/68] also read nonzipped gtfs --- docs/sandbox/DigitransitEmissions.md | 4 +- .../DigitransitEmissionsModule.java | 57 +++++++++++++------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md index 6f0654f3d7c..c6068e48831 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/DigitransitEmissions.md @@ -10,7 +10,7 @@ CO2 Emissions and ability to show them via plan queries in itinerary by Digitran This implementation enables OTP to retrieve CO2 information, which is then utilized during itinerary queries. The emissions are represented in grams per kilometer (g/Km) unit -Emissions data is located in an emissions.txt file within a gtfs.zip and has the following properties: +Emissions data is located in an emissions.txt file within a gtfs package and has the following properties: `route_id`: route id @@ -31,7 +31,7 @@ route_id,agency_id,route_short_name,type,avg,p_avg 2345,HSL,1,TRAM,0,0 ``` -Emissions data is loaded from the gtfs.zip file(s) and embedded into the graph during the build process. +Emissions data is loaded from the gtfs package and embedded into the graph during the build process. ### Configuration diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java index 8f1acac4242..00ff4d9f866 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java @@ -4,12 +4,12 @@ import dagger.Module; import jakarta.inject.Inject; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.opentripplanner.graph_builder.ConfiguredDataSource; import org.opentripplanner.graph_builder.GraphBuilderDataSources; @@ -26,6 +26,7 @@ public class DigitransitEmissionsModule implements GraphBuilderModule { private BuildConfig config; private EmissionsServiceRepository emissionsServiceRepository; private GraphBuilderDataSources dataSources; + private Map emissionsData = new HashMap<>(); @Inject public DigitransitEmissionsModule( @@ -46,36 +47,56 @@ public void buildGraph() { double carAvgOccupancy = config.digitransitEmissions.getCarAvgOccupancy(); double carAvgEmissions = carAvgCo2 / carAvgOccupancy; + for (ConfiguredDataSource gtfsData : dataSources.getGtfsConfiguredDatasource()) { + if (gtfsData.dataSource().name().contains(".zip")) { + readGtfsZip(gtfsData.dataSource().path()); + } else { + readGtfs(gtfsData.dataSource().path()); + } + } + this.emissionsServiceRepository.saveEmissionsService( - new DigitransitEmissionsService(readGtfs(), carAvgEmissions) + new DigitransitEmissionsService(this.emissionsData, carAvgEmissions) ); } } - private Map readGtfs() { - Map emissionsData = new HashMap<>(); + private void readGtfs(String filePath) { + try { + InputStream feedInfoStream = new FileInputStream(filePath + "/feed_info.txt"); + String feedId = readFeedId(feedInfoStream); + feedInfoStream.close(); + + InputStream stream = new FileInputStream(filePath + "/emissions.txt"); + readEmissions(stream, feedId); + stream.close(); + } catch (IOException e) { + LOG.error("Reading emissions data failed.", e); + } + } + private void readGtfsZip(String filePath) { try { - for (ConfiguredDataSource gtfsData : dataSources.getGtfsConfiguredDatasource()) { - ZipFile zipFile = new ZipFile(new File(gtfsData.dataSource().path()), ZipFile.OPEN_READ); - String feedId = readFeedId(zipFile); - InputStream stream = zipFile.getInputStream(zipFile.getEntry("emissions.txt")); - CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); - reader.readHeaders(); - while (reader.readRecord()) { - emissionsData.put(getKey(reader, feedId), getEmissions(reader)); - } - zipFile.close(); - } + ZipFile zipFile = new ZipFile(new File(filePath), ZipFile.OPEN_READ); + String feedId = readFeedId(zipFile.getInputStream(zipFile.getEntry("feed_info.txt"))); + InputStream stream = zipFile.getInputStream(zipFile.getEntry("emissions.txt")); + readEmissions(stream, feedId); + zipFile.close(); } catch (IOException e) { LOG.error("Reading emissions data failed.", e); } - return emissionsData; } - private String readFeedId(ZipFile zipFile) { + private void readEmissions(InputStream stream, String feedId) throws IOException { + CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); + reader.readHeaders(); + while (reader.readRecord()) { + this.emissionsData.put(getKey(reader, feedId), getEmissions(reader)); + } + } + + private String readFeedId(InputStream stream) { try { - InputStream stream = zipFile.getInputStream(zipFile.getEntry("feed_info.txt")); CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); reader.readHeaders(); reader.readRecord(); From ab4845234bfe947384ec330c58c45c8045d7829d Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 21 Sep 2023 15:40:02 +0300 Subject: [PATCH 18/68] Emissions data simplification --- docs/sandbox/DigitransitEmissions.md | 16 +++++--------- .../DigitransitEmissions.java | 12 +++++----- .../DigitransitEmissionsModule.java | 22 ++++++------------- .../DigitransitEmissionsService.java | 18 ++++----------- 4 files changed, 22 insertions(+), 46 deletions(-) diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md index c6068e48831..d7cd87ed76b 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/DigitransitEmissions.md @@ -14,21 +14,15 @@ Emissions data is located in an emissions.txt file within a gtfs package and has `route_id`: route id -`agency_id`: agency id +`avg_co2_per_vehicle_per_km`: Average carbon dioxide equivalent value for the vehicles used on the route at grams/Km units. -`route_short_name`: Short name of the route. - -`type`: Mode of transportation. - -`avg`: Average carbon dioxide equivalent value for the vehicles used on the route at grams/Km units - -`p_avg`: Average passenger count for the vehicles on the route. +`avg_passenger_count`: Average passenger count for the vehicles on the route. For example: ```csv -route_id,agency_id,route_short_name,type,avg,p_avg -1234,HSL,545,BUS,123,20 -2345,HSL,1,TRAM,0,0 +route_id,avg_co2_per_vehicle_per_km,avg_passenger_count +1234,123,20 +2345,0,0 ``` Emissions data is loaded from the gtfs package and embedded into the graph during the build process. diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java index e1fe704f663..2f4cc7b9051 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java @@ -2,16 +2,16 @@ import java.util.Objects; -public record DigitransitEmissions(double avg, int passengerAvg) { +public record DigitransitEmissions(double avgCo2PerVehiclePerKm, int avgPassengerCount) { public DigitransitEmissions { - Objects.requireNonNull(avg); - Objects.requireNonNull(passengerAvg); + Objects.requireNonNull(avgCo2PerVehiclePerKm); + Objects.requireNonNull(avgPassengerCount); } public double getEmissionsPerPassenger() { - if (this.passengerAvg <= 1) { - return this.avg; + if (this.avgPassengerCount <= 1) { + return this.avgCo2PerVehiclePerKm; } - return this.avg / this.passengerAvg; + return this.avgCo2PerVehiclePerKm / this.avgPassengerCount; } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java index 00ff4d9f866..932869578f9 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java @@ -91,7 +91,13 @@ private void readEmissions(InputStream stream, String feedId) throws IOException CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); reader.readHeaders(); while (reader.readRecord()) { - this.emissionsData.put(getKey(reader, feedId), getEmissions(reader)); + String routeId = reader.get("route_id"); + Double avgCo2PerVehiclePerKm = Double.parseDouble(reader.get("avg_co2_per_vehicle_per_km")); + int avgPassengerCount = Integer.parseInt(reader.get("avg_passenger_count")); + this.emissionsData.put( + feedId + ":" + routeId, + new DigitransitEmissions(avgCo2PerVehiclePerKm, avgPassengerCount) + ); } } @@ -106,18 +112,4 @@ private String readFeedId(InputStream stream) { throw new RuntimeException(e); } } - - private String getKey(CsvReader reader, String feedId) throws IOException { - String routeId = reader.get("route_id"); - String agencyId = reader.get("agency_id"); - String routeShortName = reader.get("route_short_name"); - String type = reader.get("type"); - return feedId + ":" + agencyId + ":" + routeId + ":" + routeShortName + ":" + type; - } - - private DigitransitEmissions getEmissions(CsvReader reader) throws IOException { - Double avg = Double.parseDouble(reader.get("avg")); - int pAvg = Integer.parseInt(reader.get("p_avg")); - return new DigitransitEmissions(avg, pAvg); - } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java index 0886d7921a4..caf966cb74b 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java @@ -60,20 +60,10 @@ private double getEmissionsForTransitItinerary(List transitLegs) { .stream() .mapToDouble(leg -> { double legDistanceInKm = leg.getDistanceMeters() / 1000; - FeedScopedId feedScopedAgencyId = leg.getAgency().getId(); - String modeName = leg.getMode().name(); - - String key = - feedScopedAgencyId + - ":" + - leg.getRoute().getId().getId() + - ":" + - leg.getRoute().getShortName() + - ":" + - modeName; - - if (key != null && this.emissions.containsKey(key)) { - return this.emissions.get(key).getEmissionsPerPassenger() * legDistanceInKm; + String feedScopedRouteId = + leg.getAgency().getId().getFeedId() + ":" + leg.getRoute().getId().getId(); + if (feedScopedRouteId != null && this.emissions.containsKey(feedScopedRouteId)) { + return this.emissions.get(feedScopedRouteId).getEmissionsPerPassenger() * legDistanceInKm; } return -1; }); From b13b7dbaa637057afbe871bdc014227443804748 Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 21 Sep 2023 16:07:54 +0300 Subject: [PATCH 19/68] Emissions tests and documentation fix --- docs/BuildConfiguration.md | 10 ++++++---- .../emissions/digitransit/EmissionsServiceTest.java | 5 ++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 62382da8dde..98d7d920725 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -56,8 +56,9 @@ Sections follow that describe particular settings in more depth. |       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | | demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | |    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | -| [digitransitEmissions](#digitransitEmissions) | `object` | Configure properties for emissions file. | *Optional* | | 2.4 | -|    url | `string` | Url to emissions json file. | *Optional* | `""` | 2.4 | +| [digitransitEmissions](#digitransitEmissions) | `object` | Configure properties for emissions file. | *Optional* | | na | +|    carAvgCo2 | `integer` | The average CO2 emissions of a car. | *Optional* | `170` | na | +|    carAvgOccupancy | `double` | The average number of passengers in a car. | *Optional* | `1.3` | na | | [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | | [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | | gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | @@ -703,7 +704,7 @@ in the source data, this should be set to 0.1.

digitransitEmissions

-**Since version:** `2.4` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` +**Since version:** `na` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` **Path:** / Configure properties for emissions file. @@ -1181,7 +1182,8 @@ case where this is not the case. } ], "digitransitEmissions" : { - "url" : "foo.bar" + "carAvgCo2" : 170, + "carAvgOccupancy" : 1.3 } } ``` diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index b87faf2ff02..0551441c2ca 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -46,8 +46,7 @@ class EmissionsServiceTest { @BeforeEach void SetUp() { Map digitransitEmissions = new HashMap<>(); - digitransitEmissions.put("F:F:1:BUS", new DigitransitEmissions(120, 12)); - digitransitEmissions.put("CAR", new DigitransitEmissions(1100, 1)); + digitransitEmissions.put("F:2", new DigitransitEmissions(120, 12)); this.eService = new DigitransitEmissionsService(digitransitEmissions, 131); } @@ -111,6 +110,6 @@ void getEmissionsForCarRoute() { .build(); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(235.83999633789062F, eService.getEmissionsForItinerary(i)); + assertEquals(28.0864F, eService.getEmissionsForItinerary(i)); } } From d9447e3b1e881a6e73ac5aaae9f9ce1b974a7a05 Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 3 Oct 2023 14:33:39 +0300 Subject: [PATCH 20/68] Documentation and test fixes --- docs/sandbox/DigitransitEmissions.md | 8 ++++---- .../ext/emissions/digitransit/EmissionsServiceTest.java | 4 ++-- .../digitransitemissions/DigitransitEmissionsConfig.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md index d7cd87ed76b..0a414178bcc 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/DigitransitEmissions.md @@ -6,9 +6,9 @@ ## Documentation -CO2 Emissions and ability to show them via plan queries in itinerary by Digitransit team. -This implementation enables OTP to retrieve CO2 information, which is then utilized during -itinerary queries. The emissions are represented in grams per kilometer (g/Km) unit +Graph build import of CO2 Emissions from GTFS data sets (through custom emissions.txt extension) +and the ability to attach them to itineraries by Digitransit team. +The emissions are represented in grams per kilometer (g/Km) unit. Emissions data is located in an emissions.txt file within a gtfs package and has the following properties: @@ -29,7 +29,7 @@ Emissions data is loaded from the gtfs package and embedded into the graph durin ### Configuration -To enable this functionality, you need to add the "Co2Emissions" feature in the +To enable this functionality, you need to enable the "Co2Emissions" feature in the `otp-config.json` file. ```json diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index 0551441c2ca..0bc6078eb45 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -51,7 +51,7 @@ void SetUp() { } @Test - void getEmissionsForItinerary() { + void testGetEmissionsForItinerary() { var route = TransitModelForTest.route(id("2")).withAgency(subject).build(); List legs = new ArrayList<>(); var pattern = TransitModelForTest @@ -86,7 +86,7 @@ void getEmissionsForItinerary() { } @Test - void getEmissionsForCarRoute() { + void testGetEmissionsForCarRoute() { var route = TransitModelForTest.route(id("2")).withAgency(subject).build(); List legs = new ArrayList<>(); var pattern = TransitModelForTest diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java index 34e16fe2347..09d98b6d942 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java @@ -13,7 +13,7 @@ public class DigitransitEmissionsConfig { public DigitransitEmissionsConfig(String parameterName, NodeAdapter root) { var c = root .of(parameterName) - .summary("Configure properties for emissions file.") + .summary("Digitransit emissions configuration.") .description( """ By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) From 1750e1562a631dbb6edacee7324ce2b7b0c6929a Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 3 Oct 2023 16:29:56 +0300 Subject: [PATCH 21/68] More tests for emissions, documentation fixed --- .../digitransit/EmissionsServiceTest.java | 96 ++++++++++++++++--- .../DigitransitEmissionsConfig.java | 2 +- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index 0bc6078eb45..aa4cec8c621 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -46,17 +46,22 @@ class EmissionsServiceTest { @BeforeEach void SetUp() { Map digitransitEmissions = new HashMap<>(); - digitransitEmissions.put("F:2", new DigitransitEmissions(120, 12)); + digitransitEmissions.put("F:1", new DigitransitEmissions(120, 12)); + digitransitEmissions.put("F:2", new DigitransitEmissions(0, 0)); this.eService = new DigitransitEmissionsService(digitransitEmissions, 131); } @Test void testGetEmissionsForItinerary() { - var route = TransitModelForTest.route(id("2")).withAgency(subject).build(); + var stopOne = TransitModelForTest.stopForTest("1:stop1", 60, 25); + var stopTwo = TransitModelForTest.stopForTest("1:stop1", 61, 25); + var stopThree = TransitModelForTest.stopForTest("1:stop1", 62, 25); + var stopPattern = TransitModelForTest.stopPattern(stopOne, stopTwo, stopThree); + var route = TransitModelForTest.route(id("1")).build(); List legs = new ArrayList<>(); var pattern = TransitModelForTest .tripPattern("1", route) - .withStopPattern(TransitModelForTest.stopPattern(3)) + .withStopPattern(stopPattern) .build(); var stoptime = new StopTime(); var stoptimes = new ArrayList(); @@ -82,12 +87,31 @@ void testGetEmissionsForItinerary() { ); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(0, eService.getEmissionsForItinerary(i)); + assertEquals(2223.902F, eService.getEmissionsForItinerary(i)); } @Test void testGetEmissionsForCarRoute() { - var route = TransitModelForTest.route(id("2")).withAgency(subject).build(); + List legs = new ArrayList<>(); + var leg = StreetLeg + .create() + .withMode(TraverseMode.CAR) + .withDistanceMeters(214.4) + .withStartTime(TIME) + .withEndTime(TIME.plus(1, ChronoUnit.HOURS)) + .build(); + legs.add(leg); + Itinerary i = new Itinerary(legs); + assertEquals(28.0864F, eService.getEmissionsForItinerary(i)); + } + + @Test + void testNoEmissionsForFeedWithoutEmissionsConfigured() { + Map digitransitEmissions = new HashMap<>(); + digitransitEmissions.put("G:1", new DigitransitEmissions(120, 12)); + this.eService = new DigitransitEmissionsService(digitransitEmissions, 131); + + var route = TransitModelForTest.route(id("1")).withAgency(subject).build(); List legs = new ArrayList<>(); var pattern = TransitModelForTest .tripPattern("1", route) @@ -97,19 +121,65 @@ void testGetEmissionsForCarRoute() { var stoptimes = new ArrayList(); stoptimes.add(stoptime); var trip = Trip - .of(FeedScopedId.parse("F:A")) + .of(FeedScopedId.parse("FOO:BAR")) .withMode(TransitMode.BUS) .withRoute(route) .build(); - var leg = StreetLeg - .create() - .withMode(TraverseMode.CAR) - .withDistanceMeters(214.4) - .withStartTime(TIME) - .withEndTime(TIME.plus(1, ChronoUnit.HOURS)) + var leg = new ScheduledTransitLeg( + new TripTimes(trip, stoptimes, new Deduplicator()), + pattern, + 0, + 2, + TIME, + TIME.plusMinutes(10), + TIME.toLocalDate(), + ZoneIds.BERLIN, + null, + null, + 100, + null + ); + legs.add(leg); + Itinerary i = new Itinerary(legs); + assertEquals(-1, eService.getEmissionsForItinerary(i)); + } + + @Test + void testZeroEmissionsForItineraryWithZeroEmissions() { + var stopOne = TransitModelForTest.stopForTest("1:stop1", 60, 25); + var stopTwo = TransitModelForTest.stopForTest("1:stop1", 61, 25); + var stopThree = TransitModelForTest.stopForTest("1:stop1", 62, 25); + var stopPattern = TransitModelForTest.stopPattern(stopOne, stopTwo, stopThree); + var route = TransitModelForTest.route(id("2")).build(); + List legs = new ArrayList<>(); + var pattern = TransitModelForTest + .tripPattern("1", route) + .withStopPattern(stopPattern) .build(); + var stoptime = new StopTime(); + var stoptimes = new ArrayList(); + stoptimes.add(stoptime); + var trip = Trip + .of(FeedScopedId.parse("FOO:BAR")) + .withMode(TransitMode.BUS) + .withRoute(route) + .build(); + var leg = new ScheduledTransitLeg( + new TripTimes(trip, stoptimes, new Deduplicator()), + pattern, + 0, + 2, + TIME, + TIME.plusMinutes(10), + TIME.toLocalDate(), + ZoneIds.BERLIN, + null, + null, + 100, + null + ); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(28.0864F, eService.getEmissionsForItinerary(i)); + assertEquals(0, eService.getEmissionsForItinerary(i)); } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java index 09d98b6d942..01083c54816 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java @@ -23,7 +23,7 @@ By specifying a URL to fetch emissions data, the program gains access to carbon ) .asObject(); - this.carAvgCo2 = c.of("carAvgCo2").summary("The average CO2 emissions of a car.").asInt(170); + this.carAvgCo2 = c.of("carAvgCo2").summary("The average CO2 emissions of a car in grams per kilometer.").asInt(170); this.carAvgOccupancy = c.of("carAvgOccupancy").summary("The average number of passengers in a car.").asDouble(1.3); From d9047017b04794532f96933fd666ab93cec0ec59 Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 4 Oct 2023 08:52:26 +0300 Subject: [PATCH 22/68] emissions test fixes, cleanup --- .../digitransit/EmissionsServiceTest.java | 15 +++++---------- .../DigitransitEmissionsConfig.java | 6 +++++- .../DigitransitEmissionsService.java | 10 +++++++--- .../apis/gtfs/generated/GraphQLDataFetchers.java | 10 ---------- 4 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index aa4cec8c621..cf8d34669ad 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.ext.emissions.digitransit; 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; @@ -59,10 +60,7 @@ void testGetEmissionsForItinerary() { var stopPattern = TransitModelForTest.stopPattern(stopOne, stopTwo, stopThree); var route = TransitModelForTest.route(id("1")).build(); List legs = new ArrayList<>(); - var pattern = TransitModelForTest - .tripPattern("1", route) - .withStopPattern(stopPattern) - .build(); + var pattern = TransitModelForTest.tripPattern("1", route).withStopPattern(stopPattern).build(); var stoptime = new StopTime(); var stoptimes = new ArrayList(); stoptimes.add(stoptime); @@ -110,7 +108,7 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { Map digitransitEmissions = new HashMap<>(); digitransitEmissions.put("G:1", new DigitransitEmissions(120, 12)); this.eService = new DigitransitEmissionsService(digitransitEmissions, 131); - + var route = TransitModelForTest.route(id("1")).withAgency(subject).build(); List legs = new ArrayList<>(); var pattern = TransitModelForTest @@ -141,7 +139,7 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { ); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(-1, eService.getEmissionsForItinerary(i)); + assertNull(eService.getEmissionsForItinerary(i)); } @Test @@ -152,10 +150,7 @@ void testZeroEmissionsForItineraryWithZeroEmissions() { var stopPattern = TransitModelForTest.stopPattern(stopOne, stopTwo, stopThree); var route = TransitModelForTest.route(id("2")).build(); List legs = new ArrayList<>(); - var pattern = TransitModelForTest - .tripPattern("1", route) - .withStopPattern(stopPattern) - .build(); + var pattern = TransitModelForTest.tripPattern("1", route).withStopPattern(stopPattern).build(); var stoptime = new StopTime(); var stoptimes = new ArrayList(); stoptimes.add(stoptime); diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java index 01083c54816..932e9263892 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java @@ -23,7 +23,11 @@ By specifying a URL to fetch emissions data, the program gains access to carbon ) .asObject(); - this.carAvgCo2 = c.of("carAvgCo2").summary("The average CO2 emissions of a car in grams per kilometer.").asInt(170); + this.carAvgCo2 = + c + .of("carAvgCo2") + .summary("The average CO2 emissions of a car in grams per kilometer.") + .asInt(170); this.carAvgOccupancy = c.of("carAvgOccupancy").summary("The average number of passengers in a car.").asDouble(1.3); diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java index caf966cb74b..d8b67b2dbba 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java @@ -4,6 +4,7 @@ import java.util.DoubleSummaryStatistics; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.DoubleStream; import org.opentripplanner.ext.flex.FlexibleTransitLeg; import org.opentripplanner.framework.lang.Sandbox; @@ -38,7 +39,8 @@ public Float getEmissionsForItinerary(Itinerary itinerary) { .toList(); if (!transitLegs.isEmpty()) { - return (float) getEmissionsForTransitItinerary(transitLegs); + Double emissions = getEmissionsForTransitItinerary(transitLegs); + return emissions != null ? emissions.floatValue() : null; } List carLegs = itinerary @@ -55,7 +57,7 @@ public Float getEmissionsForItinerary(Itinerary itinerary) { return null; } - private double getEmissionsForTransitItinerary(List transitLegs) { + private Double getEmissionsForTransitItinerary(List transitLegs) { DoubleStream emissionsStream = transitLegs .stream() .mapToDouble(leg -> { @@ -65,12 +67,14 @@ private double getEmissionsForTransitItinerary(List transitLegs) { if (feedScopedRouteId != null && this.emissions.containsKey(feedScopedRouteId)) { return this.emissions.get(feedScopedRouteId).getEmissionsPerPassenger() * legDistanceInKm; } + // Emissions value for the leg is missing return -1; }); DoubleSummaryStatistics stats = emissionsStream.summaryStatistics(); Double sum = stats.getSum(); + // Check that no emissions value is invalid and the result is a number. if (stats.getMin() < 0 || Double.isNaN(sum)) { - return -1; + return null; } return sum; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 77fd282ed34..21555ad7a92 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -2,14 +2,11 @@ package org.opentripplanner.apis.gtfs.generated; import graphql.relay.Connection; -import graphql.relay.Connection; -import graphql.relay.Edge; import graphql.relay.Edge; import graphql.schema.DataFetcher; import graphql.schema.TypeResolver; import java.util.Currency; import java.util.Map; -import java.util.Map; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.api.resource.DebugOutput; @@ -23,11 +20,7 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRoutingErrorCode; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLTransitMode; import org.opentripplanner.apis.gtfs.model.RideHailingProvider; -import org.opentripplanner.apis.gtfs.model.RouteTypeModel; -import org.opentripplanner.apis.gtfs.model.StopOnRouteModel; -import org.opentripplanner.apis.gtfs.model.StopOnTripModel; import org.opentripplanner.apis.gtfs.model.StopPosition; -import org.opentripplanner.apis.gtfs.model.UnknownModel; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.ridehailing.model.RideEstimate; import org.opentripplanner.model.StopTimesInPattern; @@ -49,8 +42,6 @@ import org.opentripplanner.routing.graphfinder.PatternAtStop; import org.opentripplanner.routing.graphfinder.PlaceAtDistance; import org.opentripplanner.routing.vehicle_parking.VehicleParking; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; @@ -59,7 +50,6 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; -import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.network.Route; From db30d397e383d4b03b7408da7e1087b4da76de03 Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 4 Oct 2023 14:35:56 +0300 Subject: [PATCH 23/68] emissions use feedScopedId, use meters instead of km, refactoring --- .../digitransit/EmissionsServiceTest.java | 23 ++++++++---- .../DigitransitEmissions.java | 35 +++++++++++++++---- .../DigitransitEmissionsModule.java | 16 +++++---- .../DigitransitEmissionsService.java | 24 +++++++------ 4 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index cf8d34669ad..933292a4231 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -46,10 +46,16 @@ class EmissionsServiceTest { @BeforeEach void SetUp() { - Map digitransitEmissions = new HashMap<>(); - digitransitEmissions.put("F:1", new DigitransitEmissions(120, 12)); - digitransitEmissions.put("F:2", new DigitransitEmissions(0, 0)); - this.eService = new DigitransitEmissionsService(digitransitEmissions, 131); + Map digitransitEmissions = new HashMap<>(); + digitransitEmissions.put( + new FeedScopedId("F", "1"), + DigitransitEmissions.newDigitransitEmissions(0.12, 12) + ); + digitransitEmissions.put( + new FeedScopedId("F", "2"), + DigitransitEmissions.newDigitransitEmissions(0, 0) + ); + this.eService = new DigitransitEmissionsService(digitransitEmissions, 0.131); } @Test @@ -105,9 +111,12 @@ void testGetEmissionsForCarRoute() { @Test void testNoEmissionsForFeedWithoutEmissionsConfigured() { - Map digitransitEmissions = new HashMap<>(); - digitransitEmissions.put("G:1", new DigitransitEmissions(120, 12)); - this.eService = new DigitransitEmissionsService(digitransitEmissions, 131); + Map digitransitEmissions = new HashMap<>(); + digitransitEmissions.put( + new FeedScopedId("G", "1"), + DigitransitEmissions.newDigitransitEmissions(0.12, 12) + ); + this.eService = new DigitransitEmissionsService(digitransitEmissions, 0.131); var route = TransitModelForTest.route(id("1")).withAgency(subject).build(); List legs = new ArrayList<>(); diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java index 2f4cc7b9051..1f37d2e5881 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java @@ -2,16 +2,39 @@ import java.util.Objects; -public record DigitransitEmissions(double avgCo2PerVehiclePerKm, int avgPassengerCount) { +public record DigitransitEmissions( + double avgCo2PerVehiclePerMeter, + int avgPassengerCount, + double emissionsPerPassengerPerMeter +) { public DigitransitEmissions { - Objects.requireNonNull(avgCo2PerVehiclePerKm); + Objects.requireNonNull(avgCo2PerVehiclePerMeter); Objects.requireNonNull(avgPassengerCount); + Objects.requireNonNull(emissionsPerPassengerPerMeter); } - public double getEmissionsPerPassenger() { - if (this.avgPassengerCount <= 1) { - return this.avgCo2PerVehiclePerKm; + public static DigitransitEmissions newDigitransitEmissions( + double avgCo2PerVehiclePerMeter, + int avgPassengerCount + ) { + return new DigitransitEmissions( + avgCo2PerVehiclePerMeter, + avgPassengerCount, + calculateEmissionsPerPassengerPerMeter(avgCo2PerVehiclePerMeter, avgPassengerCount) + ); + } + + private static double calculateEmissionsPerPassengerPerMeter( + double avgCo2PerVehiclePerMeter, + int avgPassengerCount + ) { + if (avgPassengerCount <= 1) { + return avgCo2PerVehiclePerMeter; } - return this.avgCo2PerVehiclePerKm / this.avgPassengerCount; + return avgCo2PerVehiclePerMeter / avgPassengerCount; + } + + public double getEmissionsPerPassenger() { + return this.emissionsPerPassengerPerMeter; } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java index 932869578f9..71ef36ad0af 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java @@ -16,6 +16,7 @@ import org.opentripplanner.graph_builder.model.GraphBuilderModule; import org.opentripplanner.gtfs.graphbuilder.GtfsFeedParameters; import org.opentripplanner.standalone.config.BuildConfig; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +27,7 @@ public class DigitransitEmissionsModule implements GraphBuilderModule { private BuildConfig config; private EmissionsServiceRepository emissionsServiceRepository; private GraphBuilderDataSources dataSources; - private Map emissionsData = new HashMap<>(); + private Map emissionsData = new HashMap<>(); @Inject public DigitransitEmissionsModule( @@ -43,9 +44,9 @@ public void buildGraph() { if (config.digitransitEmissions != null) { LOG.info("Start emissions building!"); - int carAvgCo2 = config.digitransitEmissions.getCarAvgCo2(); + int carAvgCo2PerKm = config.digitransitEmissions.getCarAvgCo2(); double carAvgOccupancy = config.digitransitEmissions.getCarAvgOccupancy(); - double carAvgEmissions = carAvgCo2 / carAvgOccupancy; + double carAvgEmissionsPerMeter = carAvgCo2PerKm / 1000 / carAvgOccupancy; for (ConfiguredDataSource gtfsData : dataSources.getGtfsConfiguredDatasource()) { if (gtfsData.dataSource().name().contains(".zip")) { @@ -56,7 +57,7 @@ public void buildGraph() { } this.emissionsServiceRepository.saveEmissionsService( - new DigitransitEmissionsService(this.emissionsData, carAvgEmissions) + new DigitransitEmissionsService(this.emissionsData, carAvgEmissionsPerMeter) ); } } @@ -92,11 +93,12 @@ private void readEmissions(InputStream stream, String feedId) throws IOException reader.readHeaders(); while (reader.readRecord()) { String routeId = reader.get("route_id"); - Double avgCo2PerVehiclePerKm = Double.parseDouble(reader.get("avg_co2_per_vehicle_per_km")); + Double avgCo2PerVehiclePerMeter = + Double.parseDouble(reader.get("avg_co2_per_vehicle_per_km")) / 1000; int avgPassengerCount = Integer.parseInt(reader.get("avg_passenger_count")); this.emissionsData.put( - feedId + ":" + routeId, - new DigitransitEmissions(avgCo2PerVehiclePerKm, avgPassengerCount) + new FeedScopedId(feedId, routeId), + DigitransitEmissions.newDigitransitEmissions(avgCo2PerVehiclePerMeter, avgPassengerCount) ); } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java index d8b67b2dbba..658ae5788d6 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java @@ -18,15 +18,15 @@ @Sandbox public class DigitransitEmissionsService implements Serializable, EmissionsService { - private Map emissions; + private Map emissions; private double carAvgEmissions; public DigitransitEmissionsService( - Map emissions, - double carAvgEmissions + Map emissions, + double carAvgEmissionsPerMeter ) { this.emissions = emissions; - this.carAvgEmissions = carAvgEmissions; + this.carAvgEmissions = carAvgEmissionsPerMeter; } @Override @@ -61,11 +61,15 @@ private Double getEmissionsForTransitItinerary(List transitLegs) { DoubleStream emissionsStream = transitLegs .stream() .mapToDouble(leg -> { - double legDistanceInKm = leg.getDistanceMeters() / 1000; - String feedScopedRouteId = - leg.getAgency().getId().getFeedId() + ":" + leg.getRoute().getId().getId(); + double legDistanceInMeters = leg.getDistanceMeters(); + FeedScopedId feedScopedRouteId = new FeedScopedId( + leg.getAgency().getId().getFeedId(), + leg.getRoute().getId().getId() + ); if (feedScopedRouteId != null && this.emissions.containsKey(feedScopedRouteId)) { - return this.emissions.get(feedScopedRouteId).getEmissionsPerPassenger() * legDistanceInKm; + return ( + this.emissions.get(feedScopedRouteId).getEmissionsPerPassenger() * legDistanceInMeters + ); } // Emissions value for the leg is missing return -1; @@ -83,8 +87,8 @@ private double getEmissionsForCarItinerary(List carLegs) { return carLegs .stream() .mapToDouble(leg -> { - double carLegDistanceInKm = leg.getDistanceMeters() / 1000; - return (this.carAvgEmissions * carLegDistanceInKm); + double carLegDistanceInMeters = leg.getDistanceMeters(); + return (this.carAvgEmissions * carLegDistanceInMeters); }) .sum(); } From 72088fe21f433db40f34ca5893e6431bc8e5dfb9 Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 4 Oct 2023 16:01:01 +0300 Subject: [PATCH 24/68] emissions use double instead of float, better naming --- docs/BuildConfiguration.md | 2 +- docs/sandbox/DigitransitEmissions.md | 4 ++-- .../emissions/digitransit/EmissionsServiceTest.java | 4 ++-- .../digitransitemissions/DefaultEmissionsService.java | 2 +- .../DigitransitEmissionsConfig.java | 10 +++++----- .../DigitransitEmissionsModule.java | 2 +- .../DigitransitEmissionsService.java | 7 +++---- .../ext/digitransitemissions/EmissionsFilter.java | 2 +- .../ext/digitransitemissions/EmissionsService.java | 2 +- .../org/opentripplanner/api/model/ApiItinerary.java | 2 +- .../java/org/opentripplanner/model/plan/Itinerary.java | 6 +++--- src/main/java/org/opentripplanner/model/plan/Leg.java | 2 +- 12 files changed, 22 insertions(+), 23 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 08ab9a1e2a9..a8080fcbca3 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -1180,7 +1180,7 @@ case where this is not the case. } ], "digitransitEmissions" : { - "carAvgCo2" : 170, + "carAvgCo2PerKm" : 170, "carAvgOccupancy" : 1.3 } } diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md index 0a414178bcc..826809984ec 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/DigitransitEmissions.md @@ -40,14 +40,14 @@ To enable this functionality, you need to enable the "Co2Emissions" feature in ``` Include the `digitransitEmissions` object in the `build-config.json` file. The `digitransitEmissions` object should contain parameters called -`carAvgCo2` and `carAvgOccupancy`. The `carAvgCo2` provides the average emissions value for a car and +`carAvgCo2PerKm` and `carAvgOccupancy`. The `carAvgCo2PerKm` provides the average emissions value for a car and the `carAvgOccupancy` provides the average number of passengers in a car. ```json //build-config.json { "digitransitEmissions": { - "carAvgCo2": 170, + "carAvgCo2PerKm": 170, "carAvgOccupancy": 1.3 } } diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index 933292a4231..975d590e282 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -91,7 +91,7 @@ void testGetEmissionsForItinerary() { ); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(2223.902F, eService.getEmissionsForItinerary(i)); + assertEquals(2223.902, eService.getEmissionsForItinerary(i)); } @Test @@ -106,7 +106,7 @@ void testGetEmissionsForCarRoute() { .build(); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(28.0864F, eService.getEmissionsForItinerary(i)); + assertEquals(28.0864, eService.getEmissionsForItinerary(i)); } @Test diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java index 3bbfba935cb..8b386747969 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java @@ -15,7 +15,7 @@ public DefaultEmissionsService(EmissionsServiceRepository repository) { } @Override - public Float getEmissionsForItinerary(Itinerary itinerary) { + public Double getEmissionsForItinerary(Itinerary itinerary) { Optional service = repository.retrieveEmissionsService(); if (service.isPresent()) { return service.get().getEmissionsForItinerary(itinerary); diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java index 932e9263892..e8e7745a309 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java @@ -7,7 +7,7 @@ */ public class DigitransitEmissionsConfig { - private int carAvgCo2; + private int carAvgCo2PerKm; private double carAvgOccupancy; public DigitransitEmissionsConfig(String parameterName, NodeAdapter root) { @@ -23,9 +23,9 @@ By specifying a URL to fetch emissions data, the program gains access to carbon ) .asObject(); - this.carAvgCo2 = + this.carAvgCo2PerKm = c - .of("carAvgCo2") + .of("carAvgCo2PerKm") .summary("The average CO2 emissions of a car in grams per kilometer.") .asInt(170); @@ -33,8 +33,8 @@ By specifying a URL to fetch emissions data, the program gains access to carbon c.of("carAvgOccupancy").summary("The average number of passengers in a car.").asDouble(1.3); } - public int getCarAvgCo2() { - return carAvgCo2; + public int getCarAvgCo2PerKm() { + return carAvgCo2PerKm; } public double getCarAvgOccupancy() { diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java index 71ef36ad0af..35d545c65d3 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java @@ -44,7 +44,7 @@ public void buildGraph() { if (config.digitransitEmissions != null) { LOG.info("Start emissions building!"); - int carAvgCo2PerKm = config.digitransitEmissions.getCarAvgCo2(); + int carAvgCo2PerKm = config.digitransitEmissions.getCarAvgCo2PerKm(); double carAvgOccupancy = config.digitransitEmissions.getCarAvgOccupancy(); double carAvgEmissionsPerMeter = carAvgCo2PerKm / 1000 / carAvgOccupancy; diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java index 658ae5788d6..d64db25c9a0 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java @@ -30,7 +30,7 @@ public DigitransitEmissionsService( } @Override - public Float getEmissionsForItinerary(Itinerary itinerary) { + public Double getEmissionsForItinerary(Itinerary itinerary) { List transitLegs = itinerary .getLegs() .stream() @@ -39,8 +39,7 @@ public Float getEmissionsForItinerary(Itinerary itinerary) { .toList(); if (!transitLegs.isEmpty()) { - Double emissions = getEmissionsForTransitItinerary(transitLegs); - return emissions != null ? emissions.floatValue() : null; + return getEmissionsForTransitItinerary(transitLegs); } List carLegs = itinerary @@ -52,7 +51,7 @@ public Float getEmissionsForItinerary(Itinerary itinerary) { .toList(); if (!carLegs.isEmpty()) { - return (float) getEmissionsForCarItinerary(carLegs); + return getEmissionsForCarItinerary(carLegs); } return null; } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java index 734f02e4732..bbe662d28be 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java @@ -10,7 +10,7 @@ public record EmissionsFilter(EmissionsService emissionsService) implements Itin @Override public List filter(List itineraries) { for (Itinerary i : itineraries) { - Float emissions = emissionsService.getEmissionsForItinerary(i); + Double emissions = emissionsService.getEmissionsForItinerary(i); if (emissions != null) { i.setEmissions(emissions); } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java index 60d48dbf5f6..599d30d4efd 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java @@ -5,5 +5,5 @@ @Sandbox public interface EmissionsService { - Float getEmissionsForItinerary(Itinerary itinerary); + Double getEmissionsForItinerary(Itinerary itinerary); } diff --git a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java index 40e41e56d64..96c03cfc69d 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java +++ b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java @@ -83,7 +83,7 @@ public class ApiItinerary { /** * CO2 emissions of this trip */ - public Float emissions; + public Double emissions; /** * A list of Legs. Each Leg is either a walking (cycling, car) portion of the trip, or a transit * trip on a particular vehicle. So a trip where the use walks to the Q train, transfers to the 6, diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index a6b816b904c..df4123acb99 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -51,7 +51,7 @@ public class Itinerary { /* Sandbox experimental properties */ private Float accessibilityScore; - private Float emissions; + private Double emissions; /* other properties */ @@ -592,12 +592,12 @@ public List getScheduledTransitLegs() { /** * The co2 emissions of this itinerary. */ - public void setEmissions(Float emissions) { + public void setEmissions(Double emissions) { this.emissions = emissions; } @Nullable - public Float getEmissions() { + public Double getEmissions() { return this.emissions; } } diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java index b6f80dee95d..7c8fbb03c1a 100644 --- a/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -412,7 +412,7 @@ default Float accessibilityScore() { } @Nullable - default Float emissions() { + default Double emissions() { return null; } From bf74026e56aa43b04448f9ec835fea739f2cce96 Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 10 Oct 2023 16:46:40 +0300 Subject: [PATCH 25/68] support decimals in passenger count --- .../ext/digitransitemissions/DigitransitEmissions.java | 6 +++--- .../digitransitemissions/DigitransitEmissionsModule.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java index 1f37d2e5881..d27c8906020 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java @@ -4,7 +4,7 @@ public record DigitransitEmissions( double avgCo2PerVehiclePerMeter, - int avgPassengerCount, + double avgPassengerCount, double emissionsPerPassengerPerMeter ) { public DigitransitEmissions { @@ -15,7 +15,7 @@ public record DigitransitEmissions( public static DigitransitEmissions newDigitransitEmissions( double avgCo2PerVehiclePerMeter, - int avgPassengerCount + double avgPassengerCount ) { return new DigitransitEmissions( avgCo2PerVehiclePerMeter, @@ -26,7 +26,7 @@ public static DigitransitEmissions newDigitransitEmissions( private static double calculateEmissionsPerPassengerPerMeter( double avgCo2PerVehiclePerMeter, - int avgPassengerCount + double avgPassengerCount ) { if (avgPassengerCount <= 1) { return avgCo2PerVehiclePerMeter; diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java index 35d545c65d3..95e7055887d 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java @@ -95,7 +95,7 @@ private void readEmissions(InputStream stream, String feedId) throws IOException String routeId = reader.get("route_id"); Double avgCo2PerVehiclePerMeter = Double.parseDouble(reader.get("avg_co2_per_vehicle_per_km")) / 1000; - int avgPassengerCount = Integer.parseInt(reader.get("avg_passenger_count")); + Double avgPassengerCount = Double.parseDouble(reader.get("avg_passenger_count")); this.emissionsData.put( new FeedScopedId(feedId, routeId), DigitransitEmissions.newDigitransitEmissions(avgCo2PerVehiclePerMeter, avgPassengerCount) From 12c8e21f8cfd09cd37668f3e76bfe744b3e06d31 Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 10 Oct 2023 17:02:57 +0300 Subject: [PATCH 26/68] documentation fixes --- docs/BuildConfiguration.md | 8 ++++---- docs/sandbox/DigitransitEmissions.md | 5 +++-- .../digitransitemissions/DigitransitEmissionsConfig.java | 4 ++-- .../java/org/opentripplanner/api/model/ApiItinerary.java | 2 +- .../java/org/opentripplanner/model/plan/Itinerary.java | 2 +- .../org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- src/test/resources/standalone/config/build-config.json | 2 +- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index a8080fcbca3..7cd8017554a 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -54,8 +54,8 @@ Sections follow that describe particular settings in more depth. |       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | | demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | |    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | -| [digitransitEmissions](#digitransitEmissions) | `object` | Configure properties for emissions file. | *Optional* | | na | -|    carAvgCo2 | `integer` | The average CO2 emissions of a car. | *Optional* | `170` | na | +| [digitransitEmissions](#digitransitEmissions) | `object` | Digitransit emissions configuration. | *Optional* | | na | +|    carAvgCo2PerKm | `integer` | The average CO₂ emissions of a car in grams per kilometer. | *Optional* | `170` | na | |    carAvgOccupancy | `double` | The average number of passengers in a car. | *Optional* | `1.3` | na | | [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | | [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | @@ -705,9 +705,9 @@ in the source data, this should be set to 0.1. **Since version:** `na` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` **Path:** / -Configure properties for emissions file. +Digitransit emissions configuration. -By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) +By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO₂) emissions associated with transportation modes. This data is then used to perform emission calculations for public transport modes and car travel. diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/DigitransitEmissions.md index 826809984ec..265377cf347 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/DigitransitEmissions.md @@ -1,4 +1,4 @@ -# Digitransit CO2 Emissions calculation +# Digitransit CO₂ Emissions calculation ## Contact Info @@ -6,7 +6,7 @@ ## Documentation -Graph build import of CO2 Emissions from GTFS data sets (through custom emissions.txt extension) +Graph build import of CO₂ Emissions from GTFS data sets (through custom emissions.txt extension) and the ability to attach them to itineraries by Digitransit team. The emissions are represented in grams per kilometer (g/Km) unit. @@ -23,6 +23,7 @@ For example: route_id,avg_co2_per_vehicle_per_km,avg_passenger_count 1234,123,20 2345,0,0 +3456,12.3,20.0 ``` Emissions data is loaded from the gtfs package and embedded into the graph during the build process. diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java index e8e7745a309..96e4446d882 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java @@ -16,7 +16,7 @@ public DigitransitEmissionsConfig(String parameterName, NodeAdapter root) { .summary("Digitransit emissions configuration.") .description( """ - By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO2) + By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO₂) emissions associated with transportation modes. This data is then used to perform emission calculations for public transport modes and car travel. """ @@ -26,7 +26,7 @@ By specifying a URL to fetch emissions data, the program gains access to carbon this.carAvgCo2PerKm = c .of("carAvgCo2PerKm") - .summary("The average CO2 emissions of a car in grams per kilometer.") + .summary("The average CO₂ emissions of a car in grams per kilometer.") .asInt(170); this.carAvgOccupancy = diff --git a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java index 96c03cfc69d..9a2aac6cfd8 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java +++ b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java @@ -81,7 +81,7 @@ public class ApiItinerary { public ApiItineraryFares fare = new ApiItineraryFares(Map.of(), Map.of(), null, null); /** - * CO2 emissions of this trip + * CO₂ emissions of this trip */ public Double emissions; /** diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index df4123acb99..e824d715a8f 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -590,7 +590,7 @@ public List getScheduledTransitLegs() { } /** - * The co2 emissions of this itinerary. + * The CO₂ emissions of this itinerary. */ public void setEmissions(Double emissions) { this.emissions = emissions; diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index ee5312a0588..b6da5570a19 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1467,7 +1467,7 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float - """CO2 emissions of the trip on this itinerary, in grams per person.""" + """CO₂ emissions of the trip on this itinerary, in grams per person.""" emissions: Float """ diff --git a/src/test/resources/standalone/config/build-config.json b/src/test/resources/standalone/config/build-config.json index 47402d67bec..0bd6890812c 100644 --- a/src/test/resources/standalone/config/build-config.json +++ b/src/test/resources/standalone/config/build-config.json @@ -75,7 +75,7 @@ } ], "digitransitEmissions": { - "carAvgCo2": 170, + "carAvgCo2PerKm": 170, "carAvgOccupancy": 1.3 } } From 9c6d5a1e5e464455288a4f0f2854110e94f0c0ea Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 11 Oct 2023 15:52:52 +0300 Subject: [PATCH 27/68] Use emissions data model in build --- .../digitransit/EmissionsServiceTest.java | 12 +++- .../mapping/TripRequestMapperTest.java | 6 +- .../DefaultEmissionsService.java | 25 --------- .../DefaultEmissionsServiceRepository.java | 26 --------- .../DigitransitEmissions.java | 3 +- .../DigitransitEmissionsModule.java | 16 +++--- .../DigitransitEmissionsService.java | 19 +++---- .../ext/digitransitemissions/Emissions.java | 5 ++ .../EmissionsDataModel.java | 55 +++++++++++++++++++ .../EmissionsServiceModule.java | 13 +++-- .../EmissionsServiceRepository.java | 11 ---- .../EmissionsServiceRepositoryModule.java | 10 ---- .../graph_builder/GraphBuilder.java | 6 +- .../module/configure/GraphBuilderFactory.java | 4 +- .../module/configure/GraphBuilderModules.java | 6 +- .../routing/graph/SerializedGraphObject.java | 8 +-- .../opentripplanner/standalone/OTPMain.java | 2 +- .../configure/ConstructApplication.java | 12 ++-- .../ConstructApplicationFactory.java | 9 +-- .../standalone/configure/LoadApplication.java | 10 ++-- .../configure/LoadApplicationFactory.java | 6 +- .../opentripplanner/TestServerContext.java | 6 +- .../routing/graph/GraphSerializationTest.java | 15 +++-- .../transit/speed_test/SpeedTest.java | 5 +- 24 files changed, 140 insertions(+), 150 deletions(-) delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsServiceRepository.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/Emissions.java create mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsDataModel.java delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepository.java delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepositoryModule.java diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java index 975d590e282..8395e618adf 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java @@ -16,6 +16,7 @@ import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.ext.digitransitemissions.DigitransitEmissions; import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; @@ -55,7 +56,10 @@ void SetUp() { new FeedScopedId("F", "2"), DigitransitEmissions.newDigitransitEmissions(0, 0) ); - this.eService = new DigitransitEmissionsService(digitransitEmissions, 0.131); + EmissionsDataModel emissionsDataModel = new EmissionsDataModel(); + emissionsDataModel.setDigitransitEmissions(digitransitEmissions); + emissionsDataModel.setCarAvgCo2EmissionsPerMeter(0.131); + this.eService = new DigitransitEmissionsService(emissionsDataModel); } @Test @@ -116,7 +120,11 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { new FeedScopedId("G", "1"), DigitransitEmissions.newDigitransitEmissions(0.12, 12) ); - this.eService = new DigitransitEmissionsService(digitransitEmissions, 0.131); + EmissionsDataModel emissionsDataModel = new EmissionsDataModel(); + emissionsDataModel.setDigitransitEmissions(digitransitEmissions); + emissionsDataModel.setCarAvgCo2EmissionsPerMeter(0.131); + + this.eService = new DigitransitEmissionsService(emissionsDataModel); var route = TransitModelForTest.route(id("1")).withAgency(subject).build(); List legs = new ArrayList<>(); diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java index d1df45fdf20..354b9dced60 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java @@ -24,8 +24,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.opentripplanner._support.time.ZoneIds; -import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsService; -import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.calendar.CalendarServiceData; @@ -127,7 +127,7 @@ public class TripRequestMapperTest implements PlanTestConstants { new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()), new DefaultVehiclePositionService(), new DefaultVehicleRentalService(), - new DefaultEmissionsService(new DefaultEmissionsServiceRepository()), + new DigitransitEmissionsService(), RouterConfig.DEFAULT.flexConfig(), List.of(), null diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java deleted file mode 100644 index 8b386747969..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsService.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -import jakarta.inject.Inject; -import java.io.Serializable; -import java.util.Optional; -import org.opentripplanner.model.plan.Itinerary; - -public class DefaultEmissionsService implements Serializable, EmissionsService { - - private EmissionsServiceRepository repository = null; - - @Inject - public DefaultEmissionsService(EmissionsServiceRepository repository) { - this.repository = repository; - } - - @Override - public Double getEmissionsForItinerary(Itinerary itinerary) { - Optional service = repository.retrieveEmissionsService(); - if (service.isPresent()) { - return service.get().getEmissionsForItinerary(itinerary); - } - return null; - } -} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsServiceRepository.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsServiceRepository.java deleted file mode 100644 index a23253024cb..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DefaultEmissionsServiceRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import java.io.Serializable; -import java.util.Optional; -import javax.annotation.Nonnull; - -@Singleton -public class DefaultEmissionsServiceRepository implements EmissionsServiceRepository, Serializable { - - private volatile DigitransitEmissionsService emissionsService = null; - - @Inject - public DefaultEmissionsServiceRepository() {} - - @Override - public Optional retrieveEmissionsService() { - return Optional.ofNullable(emissionsService); - } - - @Override - public void saveEmissionsService(@Nonnull DigitransitEmissionsService emissionsService) { - this.emissionsService = emissionsService; - } -} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java index d27c8906020..6c00e8fdcd5 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java @@ -6,7 +6,8 @@ public record DigitransitEmissions( double avgCo2PerVehiclePerMeter, double avgPassengerCount, double emissionsPerPassengerPerMeter -) { +) + implements Emissions { public DigitransitEmissions { Objects.requireNonNull(avgCo2PerVehiclePerMeter); Objects.requireNonNull(avgPassengerCount); diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java index 95e7055887d..e1f1fd091b4 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java @@ -25,26 +25,26 @@ public class DigitransitEmissionsModule implements GraphBuilderModule { private static final Logger LOG = LoggerFactory.getLogger(DigitransitEmissionsModule.class); private BuildConfig config; - private EmissionsServiceRepository emissionsServiceRepository; + private EmissionsDataModel emissionsDataModel; private GraphBuilderDataSources dataSources; - private Map emissionsData = new HashMap<>(); + private Map emissionsData = new HashMap<>(); @Inject public DigitransitEmissionsModule( GraphBuilderDataSources dataSources, BuildConfig config, - EmissionsServiceRepository emissionsServiceRepository + EmissionsDataModel emissionsDataModel ) { this.dataSources = dataSources; this.config = config; - this.emissionsServiceRepository = emissionsServiceRepository; + this.emissionsDataModel = emissionsDataModel; } public void buildGraph() { if (config.digitransitEmissions != null) { LOG.info("Start emissions building!"); - int carAvgCo2PerKm = config.digitransitEmissions.getCarAvgCo2PerKm(); + double carAvgCo2PerKm = config.digitransitEmissions.getCarAvgCo2PerKm(); double carAvgOccupancy = config.digitransitEmissions.getCarAvgOccupancy(); double carAvgEmissionsPerMeter = carAvgCo2PerKm / 1000 / carAvgOccupancy; @@ -55,10 +55,8 @@ public void buildGraph() { readGtfs(gtfsData.dataSource().path()); } } - - this.emissionsServiceRepository.saveEmissionsService( - new DigitransitEmissionsService(this.emissionsData, carAvgEmissionsPerMeter) - ); + this.emissionsDataModel.setEmissions(this.emissionsData); + this.emissionsDataModel.setCarAvgCo2EmissionsPerMeter(carAvgEmissionsPerMeter); } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java index d64db25c9a0..6ca20af878c 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java @@ -1,10 +1,9 @@ package org.opentripplanner.ext.digitransitemissions; -import java.io.Serializable; +import jakarta.inject.Inject; import java.util.DoubleSummaryStatistics; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.DoubleStream; import org.opentripplanner.ext.flex.FlexibleTransitLeg; import org.opentripplanner.framework.lang.Sandbox; @@ -16,17 +15,17 @@ import org.opentripplanner.transit.model.framework.FeedScopedId; @Sandbox -public class DigitransitEmissionsService implements Serializable, EmissionsService { +public class DigitransitEmissionsService implements EmissionsService { - private Map emissions; + private Map emissions; private double carAvgEmissions; - public DigitransitEmissionsService( - Map emissions, - double carAvgEmissionsPerMeter - ) { - this.emissions = emissions; - this.carAvgEmissions = carAvgEmissionsPerMeter; + public DigitransitEmissionsService() {} + + @Inject + public DigitransitEmissionsService(EmissionsDataModel emissionsDataModel) { + this.emissions = emissionsDataModel.getEmissions().get(); + this.carAvgEmissions = emissionsDataModel.getCarAvgCo2EmissionsPerMeter().get(); } @Override diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/Emissions.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/Emissions.java new file mode 100644 index 00000000000..bbc7f90008e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/Emissions.java @@ -0,0 +1,5 @@ +package org.opentripplanner.ext.digitransitemissions; + +sealed interface Emissions permits DigitransitEmissions { + double getEmissionsPerPassenger(); +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsDataModel.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsDataModel.java new file mode 100644 index 00000000000..9f71e2aae08 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsDataModel.java @@ -0,0 +1,55 @@ +package org.opentripplanner.ext.digitransitemissions; + +import jakarta.inject.Inject; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +public class EmissionsDataModel implements Serializable { + + private Map emissions; + private double carAvgCo2EmissionsPerMeter; + + @Inject + public EmissionsDataModel() {} + + public EmissionsDataModel( + Map emissions, + double carAvgCo2EmissionsPerMeter + ) { + this.emissions = emissions; + this.carAvgCo2EmissionsPerMeter = carAvgCo2EmissionsPerMeter; + } + + /** + * For testing only. + * @param digitransitEmissions + */ + public void setDigitransitEmissions( + Map digitransitEmissions + ) { + Map emissions = new HashMap<>(); + for (FeedScopedId id : digitransitEmissions.keySet()) { + emissions.put(id, digitransitEmissions.get(id)); + } + this.emissions = emissions; + } + + public void setEmissions(Map emissions) { + this.emissions = emissions; + } + + public Optional> getEmissions() { + return Optional.ofNullable(this.emissions); + } + + public void setCarAvgCo2EmissionsPerMeter(double carAvgCo2EmissionsPerMeter) { + this.carAvgCo2EmissionsPerMeter = carAvgCo2EmissionsPerMeter; + } + + public Optional getCarAvgCo2EmissionsPerMeter() { + return Optional.ofNullable(this.carAvgCo2EmissionsPerMeter); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java index 91c8b7aaafa..76f17320792 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java +++ b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java @@ -1,14 +1,19 @@ package org.opentripplanner.ext.digitransitemissions; -import dagger.Binds; import dagger.Module; +import dagger.Provides; +import jakarta.inject.Singleton; /** * The service is used during application serve phase, not loading, so we need to provide * a module for the service without the repository, which is injected from the loading phase. */ @Module -public interface EmissionsServiceModule { - @Binds - EmissionsService bindService(DefaultEmissionsService service); +public class EmissionsServiceModule { + + @Provides + @Singleton + public EmissionsService provideEmissionsService(EmissionsDataModel emissionsDataModel) { + return new DigitransitEmissionsService(emissionsDataModel); + } } diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepository.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepository.java deleted file mode 100644 index f6fcf88935c..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -import java.io.Serializable; -import java.util.Optional; -import javax.annotation.Nonnull; - -public interface EmissionsServiceRepository extends Serializable { - Optional retrieveEmissionsService(); - - void saveEmissionsService(@Nonnull DigitransitEmissionsService emissionsService); -} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepositoryModule.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepositoryModule.java deleted file mode 100644 index f14c6f77d66..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceRepositoryModule.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -import dagger.Binds; -import dagger.Module; - -@Module -public interface EmissionsServiceRepositoryModule { - @Binds - EmissionsServiceRepository bindRepository(DefaultEmissionsServiceRepository repository); -} diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index 7eba070a7c5..9f87e01cb72 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -9,7 +9,7 @@ import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.framework.lang.OtpNumberFormat; @@ -61,7 +61,7 @@ public static GraphBuilder create( Graph graph, TransitModel transitModel, WorldEnvelopeRepository worldEnvelopeRepository, - EmissionsServiceRepository emissionsServiceRepository, + EmissionsDataModel emissionsDataModel, boolean loadStreetGraph, boolean saveStreetGraph ) { @@ -81,7 +81,7 @@ public static GraphBuilder create( .worldEnvelopeRepository(worldEnvelopeRepository) .dataSources(dataSources) .timeZoneId(transitModel.getTimeZone()) - .emissionsServiceRepository(emissionsServiceRepository) + .emissionsDataModel(emissionsDataModel) .build(); var graphBuilder = factory.graphBuilder(); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java index 4c6a3fa01e1..d3a8050ff4b 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java @@ -8,7 +8,7 @@ import javax.annotation.Nullable; import org.opentripplanner.ext.dataoverlay.EdgeUpdaterModule; import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsModule; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.ext.flex.AreaStopsToVerticesMapper; import org.opentripplanner.ext.transferanalyzer.DirectTransferAnalyzer; import org.opentripplanner.graph_builder.GraphBuilder; @@ -79,6 +79,6 @@ interface Builder { GraphBuilderFactory build(); @BindsInstance - Builder emissionsServiceRepository(EmissionsServiceRepository emissionsServiceRepository); + Builder emissionsDataModel(EmissionsDataModel emissionsDataModel); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 5582931c66c..6bd372357fc 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -12,7 +12,7 @@ import org.opentripplanner.ext.dataoverlay.EdgeUpdaterModule; import org.opentripplanner.ext.dataoverlay.configure.DataOverlayFactory; import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsModule; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.ext.transferanalyzer.DirectTransferAnalyzer; import org.opentripplanner.graph_builder.ConfiguredDataSource; import org.opentripplanner.graph_builder.GraphBuilderDataSources; @@ -113,9 +113,9 @@ static GtfsModule provideGtfsModule( static DigitransitEmissionsModule provideEmissionsModule( GraphBuilderDataSources dataSources, BuildConfig config, - EmissionsServiceRepository emissionsServiceRepository + EmissionsDataModel emissionsDataModel ) { - return new DigitransitEmissionsModule(dataSources, config, emissionsServiceRepository); + return new DigitransitEmissionsModule(dataSources, config, emissionsDataModel); } @Provides diff --git a/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java b/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java index 49ca0d0ae30..dca188953f7 100644 --- a/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java +++ b/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java @@ -17,7 +17,7 @@ import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.datastore.api.DataSource; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.framework.geometry.CompactElevationProfile; import org.opentripplanner.framework.lang.OtpNumberFormat; @@ -76,7 +76,7 @@ public class SerializedGraphObject implements Serializable { public final DataImportIssueSummary issueSummary; private final int stopLocationCounter; private final int routingTripPatternCounter; - public final EmissionsServiceRepository emissionsServiceRepository; + public final EmissionsDataModel emissionsDataModel; public SerializedGraphObject( Graph graph, @@ -85,7 +85,7 @@ public SerializedGraphObject( BuildConfig buildConfig, RouterConfig routerConfig, DataImportIssueSummary issueSummary, - EmissionsServiceRepository emissionsServiceRepository + EmissionsDataModel emissionsDataModel ) { this.graph = graph; this.edges = graph.getEdges(); @@ -94,7 +94,7 @@ public SerializedGraphObject( this.buildConfig = buildConfig; this.routerConfig = routerConfig; this.issueSummary = issueSummary; - this.emissionsServiceRepository = emissionsServiceRepository; + this.emissionsDataModel = emissionsDataModel; this.allTransitSubModes = SubMode.listAllCachedSubModes(); this.stopLocationCounter = StopLocation.indexCounter(); this.routingTripPatternCounter = RoutingTripPattern.indexCounter(); diff --git a/src/main/java/org/opentripplanner/standalone/OTPMain.java b/src/main/java/org/opentripplanner/standalone/OTPMain.java index 018388acdd0..65b40dc3630 100644 --- a/src/main/java/org/opentripplanner/standalone/OTPMain.java +++ b/src/main/java/org/opentripplanner/standalone/OTPMain.java @@ -152,7 +152,7 @@ private static void startOTPServer(CommandLineParameters cli) { config.buildConfig(), config.routerConfig(), DataImportIssueSummary.combine(graphBuilder.issueSummary(), app.dataImportIssueSummary()), - app.emissionsServiceRepository() + app.emissionsDataModel() ) .save(app.graphOutputDataSource()); // Log size info for the deduplicator diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index ed184b92559..2845b2b974c 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -3,7 +3,7 @@ import jakarta.ws.rs.core.Application; import javax.annotation.Nullable; import org.opentripplanner.datastore.api.DataSource; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.transmodelapi.TransmodelAPI; import org.opentripplanner.framework.application.LogMDCSupport; @@ -72,7 +72,7 @@ public class ConstructApplication { ConfigModel config, GraphBuilderDataSources graphBuilderDataSources, DataImportIssueSummary issueSummary, - EmissionsServiceRepository emissionsServiceRepository + EmissionsDataModel emissionsDataModel ) { this.cli = cli; this.graphBuilderDataSources = graphBuilderDataSources; @@ -89,7 +89,7 @@ public class ConstructApplication { .transitModel(transitModel) .graphVisualizer(graphVisualizer) .worldEnvelopeRepository(worldEnvelopeRepository) - .emissionsServiceRepository(emissionsServiceRepository) + .emissionsDataModel(emissionsDataModel) .dataImportIssueSummary(issueSummary) .build(); } @@ -121,7 +121,7 @@ public GraphBuilder createGraphBuilder() { graph(), transitModel(), factory.worldEnvelopeRepository(), - factory.emissionsServiceRepository(), + factory.emissionsDataModel(), cli.doLoadStreetGraph(), cli.doSaveStreetGraph() ); @@ -293,7 +293,7 @@ private void createMetricsLogging() { factory.metricsLogging(); } - public EmissionsServiceRepository emissionsServiceRepository() { - return factory.emissionsServiceRepository(); + public EmissionsDataModel emissionsDataModel() { + return factory.emissionsDataModel(); } } diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index 500c84236c5..c58477afbed 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -4,9 +4,8 @@ import dagger.Component; import jakarta.inject.Singleton; import javax.annotation.Nullable; -import org.opentripplanner.ext.digitransitemissions.EmissionsService; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.ext.digitransitemissions.EmissionsServiceModule; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.ext.ridehailing.configure.RideHailingServicesModule; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.raptor.configure.RaptorConfig; @@ -62,6 +61,7 @@ public interface ConstructApplicationFactory { VehicleRentalRepository vehicleRentalRepository(); VehicleRentalService vehicleRentalService(); DataImportIssueSummary dataImportIssueSummary(); + EmissionsDataModel emissionsDataModel(); @Nullable GraphVisualizer graphVisualizer(); @@ -71,9 +71,6 @@ public interface ConstructApplicationFactory { MetricsLogging metricsLogging(); - EmissionsServiceRepository emissionsServiceRepository(); - EmissionsService emissionsService(); - @Component.Builder interface Builder { @BindsInstance @@ -95,7 +92,7 @@ interface Builder { Builder dataImportIssueSummary(DataImportIssueSummary issueSummary); @BindsInstance - Builder emissionsServiceRepository(EmissionsServiceRepository emissionsServiceRepository); + Builder emissionsDataModel(EmissionsDataModel emissionsDataModel); ConstructApplicationFactory build(); } diff --git a/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java b/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java index 6fae77f94de..a99065704ba 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java @@ -1,7 +1,7 @@ package org.opentripplanner.standalone.configure; import org.opentripplanner.datastore.api.DataSource; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.graph_builder.GraphBuilderDataSources; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.routing.graph.Graph; @@ -54,7 +54,7 @@ public ConstructApplication appConstruction(SerializedGraphObject obj) { obj.transitModel, obj.worldEnvelopeRepository, obj.issueSummary, - obj.emissionsServiceRepository + obj.emissionsDataModel ); } @@ -65,7 +65,7 @@ public ConstructApplication appConstruction() { factory.emptyTransitModel(), factory.emptyWorldEnvelopeRepository(), DataImportIssueSummary.empty(), - factory.emptyEmissionsServiceRepository() + factory.emptyEmissionsDataModel() ); } @@ -85,7 +85,7 @@ private ConstructApplication createAppConstruction( TransitModel transitModel, WorldEnvelopeRepository worldEnvelopeRepository, DataImportIssueSummary issueSummary, - EmissionsServiceRepository emissionsServiceRepository + EmissionsDataModel emissionsDataModel ) { return new ConstructApplication( cli, @@ -95,7 +95,7 @@ private ConstructApplication createAppConstruction( config(), graphBuilderDataSources(), issueSummary, - emissionsServiceRepository + emissionsDataModel ); } } diff --git a/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java index 6230c8c1fd6..3562163e59d 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java @@ -6,8 +6,7 @@ import org.opentripplanner.datastore.OtpDataStore; import org.opentripplanner.datastore.configure.DataStoreModule; import org.opentripplanner.ext.datastore.gs.GsDataSourceModule; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepositoryModule; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.graph_builder.GraphBuilderDataSources; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; @@ -27,7 +26,6 @@ DataStoreModule.class, GsDataSourceModule.class, WorldEnvelopeRepositoryModule.class, - EmissionsServiceRepositoryModule.class, } ) public interface LoadApplicationFactory { @@ -48,7 +46,7 @@ public interface LoadApplicationFactory { GraphBuilderDataSources graphBuilderDataSources(); @Singleton - EmissionsServiceRepository emptyEmissionsServiceRepository(); + EmissionsDataModel emptyEmissionsDataModel(); @Component.Builder interface Builder { diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index 14212dd369f..7e0560018d8 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -4,8 +4,8 @@ import io.micrometer.core.instrument.Metrics; import java.util.List; -import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsService; -import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.ext.digitransitemissions.EmissionsService; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.graph.Graph; @@ -67,6 +67,6 @@ public static VehicleRentalService createVehicleRentalService() { } public static EmissionsService createEmissionsService() { - return new DefaultEmissionsService(new DefaultEmissionsServiceRepository()); + return new DigitransitEmissionsService(); } } diff --git a/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java b/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java index 9b707f7202c..493e3bd3d16 100644 --- a/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java +++ b/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java @@ -19,8 +19,7 @@ import org.opentripplanner.TestOtpModel; import org.opentripplanner.datastore.api.FileType; import org.opentripplanner.datastore.file.FileDataSource; -import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsServiceRepository; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; +import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.framework.geometry.HashGridSpatialIndex; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; @@ -63,8 +62,8 @@ public class GraphSerializationTest { public void testRoundTripSerializationForGTFSGraph() throws Exception { TestOtpModel model = ConstantsForTests.buildNewPortlandGraph(true); var weRepo = new DefaultWorldEnvelopeRepository(); - var emissionsRepo = new DefaultEmissionsServiceRepository(); - testRoundTrip(model.graph(), model.transitModel(), weRepo, emissionsRepo); + var emissionsDataModel = new EmissionsDataModel(); + testRoundTrip(model.graph(), model.transitModel(), weRepo, emissionsDataModel); } /** @@ -74,8 +73,8 @@ public void testRoundTripSerializationForGTFSGraph() throws Exception { public void testRoundTripSerializationForNetexGraph() throws Exception { TestOtpModel model = ConstantsForTests.buildNewMinimalNetexGraph(); var worldEnvelopeRepository = new DefaultWorldEnvelopeRepository(); - var emissionsRepo = new DefaultEmissionsServiceRepository(); - testRoundTrip(model.graph(), model.transitModel(), worldEnvelopeRepository, emissionsRepo); + var emissionsDataModel = new EmissionsDataModel(); + testRoundTrip(model.graph(), model.transitModel(), worldEnvelopeRepository, emissionsDataModel); } // Ideally we'd also test comparing two separate but identical complex graphs, built separately from the same inputs. @@ -174,7 +173,7 @@ private void testRoundTrip( Graph originalGraph, TransitModel originalTransitModel, WorldEnvelopeRepository worldEnvelopeRepository, - EmissionsServiceRepository emissionsServiceRepository + EmissionsDataModel emissionsDataModel ) throws Exception { // Now round-trip the graph through serialization. File tempFile = TempFile.createTempFile("graph", "pdx"); @@ -185,7 +184,7 @@ private void testRoundTrip( BuildConfig.DEFAULT, RouterConfig.DEFAULT, DataImportIssueSummary.empty(), - emissionsServiceRepository + emissionsDataModel ); serializedObj.save(new FileDataSource(tempFile, FileType.GRAPH)); SerializedGraphObject deserializedGraph = SerializedGraphObject.load(tempFile); diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index 6dae1ef4153..5d4c0fa7b40 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -15,9 +15,6 @@ import java.util.function.Predicate; import org.opentripplanner.TestServerContext; import org.opentripplanner.datastore.OtpDataStore; -import org.opentripplanner.ext.digitransitemissions.DefaultEmissionsServiceRepository; -import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceRepository; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.configure.RaptorConfig; @@ -116,7 +113,7 @@ public SpeedTest( TestServerContext.createWorldEnvelopeService(), TestServerContext.createVehiclePositionService(), TestServerContext.createVehicleRentalService(), - null, + TestServerContext.createEmissionsService(), config.flexConfig, List.of(), null From 4a00f674434a6fb6f8cd9fdafb1f3f5ed48564b6 Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 18 Oct 2023 15:18:35 +0300 Subject: [PATCH 28/68] Emissions unit, type, service and package updated --- docs/BuildConfiguration.md | 30 +++--- docs/Configuration.md | 2 +- .../{DigitransitEmissions.md => Emissions.md} | 12 +-- ...onsServiceTest.java => EmissionsTest.java} | 53 +++++------ .../mapping/TripRequestMapperTest.java | 5 +- .../DigitransitEmissions.java | 41 -------- .../DigitransitEmissionsService.java | 93 ------------------ .../ext/digitransitemissions/Emissions.java | 5 - .../EmissionsDataModel.java | 55 ----------- .../digitransitemissions/EmissionsFilter.java | 20 ---- .../EmissionsService.java | 9 -- .../ext/emissions/EmissionType.java | 5 + .../EmissionsConfig.java} | 10 +- .../ext/emissions/EmissionsDataModel.java | 37 ++++++++ .../ext/emissions/EmissionsDataService.java | 42 ++++++++ .../ext/emissions/EmissionsFilter.java | 95 +++++++++++++++++++ .../EmissionsModule.java} | 32 ++++--- .../ext/emissions/EmissionsService.java | 15 +++ .../EmissionsServiceModule.java | 4 +- .../api/mapping/EmissionsMapper.java | 14 +++ .../api/mapping/ItineraryMapper.java | 4 +- .../api/model/ApiEmissions.java | 11 +++ .../api/model/ApiItinerary.java | 4 +- .../apis/gtfs/datafetchers/ItineraryImpl.java | 20 +++- .../gtfs/generated/GraphQLDataFetchers.java | 2 +- .../apis/gtfs/generated/GraphQLTypes.java | 1 - .../framework/application/OTPFeature.java | 2 +- .../graph_builder/GraphBuilder.java | 2 +- .../module/configure/GraphBuilderFactory.java | 6 +- .../module/configure/GraphBuilderModules.java | 8 +- .../opentripplanner/model/plan/Emissions.java | 17 ++++ .../opentripplanner/model/plan/Itinerary.java | 8 +- .../RouteRequestToFilterChainMapper.java | 2 +- .../routing/graph/SerializedGraphObject.java | 2 +- .../api/OtpServerRequestContext.java | 2 +- .../standalone/config/BuildConfig.java | 6 +- .../configure/ConstructApplication.java | 2 +- .../ConstructApplicationFactory.java | 4 +- .../configure/ConstructApplicationModule.java | 2 +- .../standalone/configure/LoadApplication.java | 2 +- .../configure/LoadApplicationFactory.java | 2 +- .../server/DefaultServerRequestContext.java | 2 +- .../opentripplanner/apis/gtfs/schema.graphqls | 11 ++- .../opentripplanner/TestServerContext.java | 7 +- .../routing/graph/GraphSerializationTest.java | 2 +- .../standalone/config/build-config.json | 2 +- 46 files changed, 371 insertions(+), 341 deletions(-) rename docs/sandbox/{DigitransitEmissions.md => Emissions.md} (81%) rename src/ext-test/java/org/opentripplanner/ext/emissions/{digitransit/EmissionsServiceTest.java => EmissionsTest.java} (76%) delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/Emissions.java delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsDataModel.java delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java delete mode 100644 src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionType.java rename src/ext/java/org/opentripplanner/ext/{digitransitemissions/DigitransitEmissionsConfig.java => emissions/EmissionsConfig.java} (73%) create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java rename src/ext/java/org/opentripplanner/ext/{digitransitemissions/DigitransitEmissionsModule.java => emissions/EmissionsModule.java} (78%) create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java rename src/ext/java/org/opentripplanner/ext/{digitransitemissions => emissions}/EmissionsServiceModule.java (78%) create mode 100644 src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java create mode 100644 src/main/java/org/opentripplanner/api/model/ApiEmissions.java create mode 100644 src/main/java/org/opentripplanner/model/plan/Emissions.java diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 7cd8017554a..3fcedbea669 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -54,10 +54,10 @@ Sections follow that describe particular settings in more depth. |       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | | demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | |    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | -| [digitransitEmissions](#digitransitEmissions) | `object` | Digitransit emissions configuration. | *Optional* | | na | +| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | +| [emissions](#emissions) | `object` | Emissions configuration. | *Optional* | | na | |    carAvgCo2PerKm | `integer` | The average CO₂ emissions of a car in grams per kilometer. | *Optional* | `170` | na | |    carAvgOccupancy | `double` | The average number of passengers in a car. | *Optional* | `1.3` | na | -| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | | [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | | gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | |    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | @@ -700,18 +700,6 @@ values are defined in meters in the source data. If, for example, decimetres are in the source data, this should be set to 0.1. -

digitransitEmissions

- -**Since version:** `na` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` -**Path:** / - -Digitransit emissions configuration. - -By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO₂) -emissions associated with transportation modes. This data is then used -to perform emission calculations for public transport modes and car travel. - -

elevationBucket

**Since version:** `na` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` @@ -738,6 +726,18 @@ for the next graph build operation. You should add the `--cache ` com to specify your NED tile cache location. +

emissions

+ +**Since version:** `na` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` +**Path:** / + +Emissions configuration. + +By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO₂) +emissions associated with transportation modes. This data is then used +to perform emission calculations for public transport modes and car travel. + +

discardMinTransferTimes

**Since version:** `2.3` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `false` @@ -1179,7 +1179,7 @@ case where this is not the case. } } ], - "digitransitEmissions" : { + "emissions" : { "carAvgCo2PerKm" : 170, "carAvgOccupancy" : 1.3 } diff --git a/docs/Configuration.md b/docs/Configuration.md index a30417b9293..e3b851652f2 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -235,7 +235,7 @@ Here is a list of all features which can be toggled on/off and their default val | `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | | `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | | `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | -| `Co2Emissions` | Enable emissions calculation and data handling. | | ✓️ | +| `Co2Emissions` | Enable the emissions sandbox module. | | ✓️ | | `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | | `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | | `FlexRouting` | Enable FLEX routing. | | ✓️ | diff --git a/docs/sandbox/DigitransitEmissions.md b/docs/sandbox/Emissions.md similarity index 81% rename from docs/sandbox/DigitransitEmissions.md rename to docs/sandbox/Emissions.md index 265377cf347..f0a135eff10 100644 --- a/docs/sandbox/DigitransitEmissions.md +++ b/docs/sandbox/Emissions.md @@ -1,4 +1,4 @@ -# Digitransit CO₂ Emissions calculation +# CO₂ Emissions calculation ## Contact Info @@ -10,7 +10,7 @@ Graph build import of CO₂ Emissions from GTFS data sets (through custom emissi and the ability to attach them to itineraries by Digitransit team. The emissions are represented in grams per kilometer (g/Km) unit. -Emissions data is located in an emissions.txt file within a gtfs package and has the following properties: +Emissions data is located in an emissions.txt file within a gtfs package and has the following columns: `route_id`: route id @@ -39,15 +39,15 @@ To enable this functionality, you need to enable the "Co2Emissions" feature in "Co2Emissions": true } ``` -Include the `digitransitEmissions` object in the -`build-config.json` file. The `digitransitEmissions` object should contain parameters called -`carAvgCo2PerKm` and `carAvgOccupancy`. The `carAvgCo2PerKm` provides the average emissions value for a car and +Include the `emissions` object in the +`build-config.json` file. The `emissions` object should contain parameters called +`carAvgCo2PerKm` and `carAvgOccupancy`. The `carAvgCo2PerKm` provides the average emissions value for a car in g/km and the `carAvgOccupancy` provides the average number of passengers in a car. ```json //build-config.json { - "digitransitEmissions": { + "emissions": { "carAvgCo2PerKm": 170, "carAvgOccupancy": 1.3 } diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java similarity index 76% rename from src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java rename to src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 8395e618adf..787b78664a7 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/digitransit/EmissionsServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -1,8 +1,8 @@ -package org.opentripplanner.ext.emissions.digitransit; +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 static shadow.org.assertj.core.api.AssertionsForClassTypes.assertThat; import java.time.OffsetDateTime; import java.time.ZonedDateTime; @@ -14,9 +14,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner._support.time.ZoneIds; -import org.opentripplanner.ext.digitransitemissions.DigitransitEmissions; -import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; @@ -31,9 +28,10 @@ import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; -class EmissionsServiceTest { +class EmissionsTest { - private DigitransitEmissionsService eService; + private EmissionsDataService eService; + private EmissionsFilter emissionsFilter; static final ZonedDateTime TIME = OffsetDateTime .parse("2023-07-20T17:49:06+03:00") @@ -47,19 +45,14 @@ class EmissionsServiceTest { @BeforeEach void SetUp() { - Map digitransitEmissions = new HashMap<>(); - digitransitEmissions.put( - new FeedScopedId("F", "1"), - DigitransitEmissions.newDigitransitEmissions(0.12, 12) - ); - digitransitEmissions.put( - new FeedScopedId("F", "2"), - DigitransitEmissions.newDigitransitEmissions(0, 0) - ); + Map emissions = new HashMap<>(); + emissions.put(new FeedScopedId("F", "1"), (0.12 / 12)); + emissions.put(new FeedScopedId("F", "2"), 0.0); EmissionsDataModel emissionsDataModel = new EmissionsDataModel(); - emissionsDataModel.setDigitransitEmissions(digitransitEmissions); - emissionsDataModel.setCarAvgCo2EmissionsPerMeter(0.131); - this.eService = new DigitransitEmissionsService(emissionsDataModel); + emissionsDataModel.setCo2Emissions(emissions); + emissionsDataModel.setCarAvgCo2PerMeter(0.131); + this.eService = new EmissionsDataService(emissionsDataModel); + this.emissionsFilter = new EmissionsFilter(eService); } @Test @@ -95,7 +88,7 @@ void testGetEmissionsForItinerary() { ); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(2223.902, eService.getEmissionsForItinerary(i)); + assertEquals(2223.902, emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).get()); } @Test @@ -110,21 +103,19 @@ void testGetEmissionsForCarRoute() { .build(); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(28.0864, eService.getEmissionsForItinerary(i)); + assertEquals(28.0864, emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).get()); } @Test void testNoEmissionsForFeedWithoutEmissionsConfigured() { - Map digitransitEmissions = new HashMap<>(); - digitransitEmissions.put( - new FeedScopedId("G", "1"), - DigitransitEmissions.newDigitransitEmissions(0.12, 12) - ); + Map emissions = new HashMap<>(); + emissions.put(new FeedScopedId("G", "1"), (0.12 / 12)); EmissionsDataModel emissionsDataModel = new EmissionsDataModel(); - emissionsDataModel.setDigitransitEmissions(digitransitEmissions); - emissionsDataModel.setCarAvgCo2EmissionsPerMeter(0.131); + emissionsDataModel.setCo2Emissions(emissions); + emissionsDataModel.setCarAvgCo2PerMeter(0.131); - this.eService = new DigitransitEmissionsService(emissionsDataModel); + this.eService = new EmissionsDataService(emissionsDataModel); + this.emissionsFilter = new EmissionsFilter(this.eService); var route = TransitModelForTest.route(id("1")).withAgency(subject).build(); List legs = new ArrayList<>(); @@ -156,7 +147,7 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { ); legs.add(leg); Itinerary i = new Itinerary(legs); - assertNull(eService.getEmissionsForItinerary(i)); + assertThat(emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2)).isEmpty(); } @Test @@ -192,6 +183,6 @@ void testZeroEmissionsForItineraryWithZeroEmissions() { ); legs.add(leg); Itinerary i = new Itinerary(legs); - assertEquals(0, eService.getEmissionsForItinerary(i)); + assertEquals(0, emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).get()); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java index 354b9dced60..7882fb746b3 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java @@ -24,8 +24,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.opentripplanner._support.time.ZoneIds; -import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsDataService; import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.calendar.CalendarServiceData; @@ -127,7 +126,7 @@ public class TripRequestMapperTest implements PlanTestConstants { new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()), new DefaultVehiclePositionService(), new DefaultVehicleRentalService(), - new DigitransitEmissionsService(), + new EmissionsDataService(), RouterConfig.DEFAULT.flexConfig(), List.of(), null diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java deleted file mode 100644 index 6c00e8fdcd5..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissions.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -import java.util.Objects; - -public record DigitransitEmissions( - double avgCo2PerVehiclePerMeter, - double avgPassengerCount, - double emissionsPerPassengerPerMeter -) - implements Emissions { - public DigitransitEmissions { - Objects.requireNonNull(avgCo2PerVehiclePerMeter); - Objects.requireNonNull(avgPassengerCount); - Objects.requireNonNull(emissionsPerPassengerPerMeter); - } - - public static DigitransitEmissions newDigitransitEmissions( - double avgCo2PerVehiclePerMeter, - double avgPassengerCount - ) { - return new DigitransitEmissions( - avgCo2PerVehiclePerMeter, - avgPassengerCount, - calculateEmissionsPerPassengerPerMeter(avgCo2PerVehiclePerMeter, avgPassengerCount) - ); - } - - private static double calculateEmissionsPerPassengerPerMeter( - double avgCo2PerVehiclePerMeter, - double avgPassengerCount - ) { - if (avgPassengerCount <= 1) { - return avgCo2PerVehiclePerMeter; - } - return avgCo2PerVehiclePerMeter / avgPassengerCount; - } - - public double getEmissionsPerPassenger() { - return this.emissionsPerPassengerPerMeter; - } -} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java deleted file mode 100644 index 6ca20af878c..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsService.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -import jakarta.inject.Inject; -import java.util.DoubleSummaryStatistics; -import java.util.List; -import java.util.Map; -import java.util.stream.DoubleStream; -import org.opentripplanner.ext.flex.FlexibleTransitLeg; -import org.opentripplanner.framework.lang.Sandbox; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.ScheduledTransitLeg; -import org.opentripplanner.model.plan.StreetLeg; -import org.opentripplanner.model.plan.TransitLeg; -import org.opentripplanner.street.search.TraverseMode; -import org.opentripplanner.transit.model.framework.FeedScopedId; - -@Sandbox -public class DigitransitEmissionsService implements EmissionsService { - - private Map emissions; - private double carAvgEmissions; - - public DigitransitEmissionsService() {} - - @Inject - public DigitransitEmissionsService(EmissionsDataModel emissionsDataModel) { - this.emissions = emissionsDataModel.getEmissions().get(); - this.carAvgEmissions = emissionsDataModel.getCarAvgCo2EmissionsPerMeter().get(); - } - - @Override - public Double getEmissionsForItinerary(Itinerary itinerary) { - List transitLegs = itinerary - .getLegs() - .stream() - .filter(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg) - .map(TransitLeg.class::cast) - .toList(); - - if (!transitLegs.isEmpty()) { - return getEmissionsForTransitItinerary(transitLegs); - } - - List carLegs = itinerary - .getLegs() - .stream() - .filter(l -> l instanceof StreetLeg) - .map(StreetLeg.class::cast) - .filter(leg -> leg.getMode() == TraverseMode.CAR) - .toList(); - - if (!carLegs.isEmpty()) { - return getEmissionsForCarItinerary(carLegs); - } - return null; - } - - private Double getEmissionsForTransitItinerary(List transitLegs) { - DoubleStream emissionsStream = transitLegs - .stream() - .mapToDouble(leg -> { - double legDistanceInMeters = leg.getDistanceMeters(); - FeedScopedId feedScopedRouteId = new FeedScopedId( - leg.getAgency().getId().getFeedId(), - leg.getRoute().getId().getId() - ); - if (feedScopedRouteId != null && this.emissions.containsKey(feedScopedRouteId)) { - return ( - this.emissions.get(feedScopedRouteId).getEmissionsPerPassenger() * legDistanceInMeters - ); - } - // Emissions value for the leg is missing - return -1; - }); - DoubleSummaryStatistics stats = emissionsStream.summaryStatistics(); - Double sum = stats.getSum(); - // Check that no emissions value is invalid and the result is a number. - if (stats.getMin() < 0 || Double.isNaN(sum)) { - return null; - } - return sum; - } - - private double getEmissionsForCarItinerary(List carLegs) { - return carLegs - .stream() - .mapToDouble(leg -> { - double carLegDistanceInMeters = leg.getDistanceMeters(); - return (this.carAvgEmissions * carLegDistanceInMeters); - }) - .sum(); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/Emissions.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/Emissions.java deleted file mode 100644 index bbc7f90008e..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/Emissions.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -sealed interface Emissions permits DigitransitEmissions { - double getEmissionsPerPassenger(); -} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsDataModel.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsDataModel.java deleted file mode 100644 index 9f71e2aae08..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsDataModel.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -import jakarta.inject.Inject; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import org.opentripplanner.transit.model.framework.FeedScopedId; - -public class EmissionsDataModel implements Serializable { - - private Map emissions; - private double carAvgCo2EmissionsPerMeter; - - @Inject - public EmissionsDataModel() {} - - public EmissionsDataModel( - Map emissions, - double carAvgCo2EmissionsPerMeter - ) { - this.emissions = emissions; - this.carAvgCo2EmissionsPerMeter = carAvgCo2EmissionsPerMeter; - } - - /** - * For testing only. - * @param digitransitEmissions - */ - public void setDigitransitEmissions( - Map digitransitEmissions - ) { - Map emissions = new HashMap<>(); - for (FeedScopedId id : digitransitEmissions.keySet()) { - emissions.put(id, digitransitEmissions.get(id)); - } - this.emissions = emissions; - } - - public void setEmissions(Map emissions) { - this.emissions = emissions; - } - - public Optional> getEmissions() { - return Optional.ofNullable(this.emissions); - } - - public void setCarAvgCo2EmissionsPerMeter(double carAvgCo2EmissionsPerMeter) { - this.carAvgCo2EmissionsPerMeter = carAvgCo2EmissionsPerMeter; - } - - public Optional getCarAvgCo2EmissionsPerMeter() { - return Optional.ofNullable(this.carAvgCo2EmissionsPerMeter); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java deleted file mode 100644 index bbe662d28be..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsFilter.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -import java.util.List; -import org.opentripplanner.framework.lang.Sandbox; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; - -@Sandbox -public record EmissionsFilter(EmissionsService emissionsService) implements ItineraryListFilter { - @Override - public List filter(List itineraries) { - for (Itinerary i : itineraries) { - Double emissions = emissionsService.getEmissionsForItinerary(i); - if (emissions != null) { - i.setEmissions(emissions); - } - } - return itineraries; - } -} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java deleted file mode 100644 index 599d30d4efd..00000000000 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsService.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.opentripplanner.ext.digitransitemissions; - -import org.opentripplanner.framework.lang.Sandbox; -import org.opentripplanner.model.plan.Itinerary; - -@Sandbox -public interface EmissionsService { - Double getEmissionsForItinerary(Itinerary itinerary); -} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionType.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionType.java new file mode 100644 index 00000000000..df904a333bf --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionType.java @@ -0,0 +1,5 @@ +package org.opentripplanner.ext.emissions; + +public enum EmissionType { + CO2, +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsConfig.java similarity index 73% rename from src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java rename to src/ext/java/org/opentripplanner/ext/emissions/EmissionsConfig.java index 96e4446d882..9f4a29610b1 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsConfig.java @@ -1,19 +1,19 @@ -package org.opentripplanner.ext.digitransitemissions; +package org.opentripplanner.ext.emissions; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; /** - * This class is responsible for mapping Digitransit emissions configuration into Digitransit emissions parameters. + * This class is responsible for mapping emissions configuration into emissions parameters. */ -public class DigitransitEmissionsConfig { +public class EmissionsConfig { private int carAvgCo2PerKm; private double carAvgOccupancy; - public DigitransitEmissionsConfig(String parameterName, NodeAdapter root) { + public EmissionsConfig(String parameterName, NodeAdapter root) { var c = root .of(parameterName) - .summary("Digitransit emissions configuration.") + .summary("Emissions configuration.") .description( """ By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO₂) diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java new file mode 100644 index 00000000000..dcfa7db0143 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java @@ -0,0 +1,37 @@ +package org.opentripplanner.ext.emissions; + +import jakarta.inject.Inject; +import java.io.Serializable; +import java.util.Map; +import java.util.Optional; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +public class EmissionsDataModel implements Serializable { + + private Map co2Emissions; + private double carAvgCo2PerMeter; + + @Inject + public EmissionsDataModel() {} + + public EmissionsDataModel(Map co2Emissions, double carAvgCo2PerMeter) { + this.co2Emissions = co2Emissions; + this.carAvgCo2PerMeter = carAvgCo2PerMeter; + } + + public void setCo2Emissions(Map co2Emissions) { + this.co2Emissions = co2Emissions; + } + + public void setCarAvgCo2PerMeter(double carAvgCo2PerMeter) { + this.carAvgCo2PerMeter = carAvgCo2PerMeter; + } + + public Double getCarAvgCo2PerMeter() { + return this.carAvgCo2PerMeter; + } + + public Optional getCO2EmissionsById(FeedScopedId feedScopedRouteId) { + return Optional.ofNullable(this.co2Emissions.get(feedScopedRouteId)); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java new file mode 100644 index 00000000000..53b7f4455d4 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java @@ -0,0 +1,42 @@ +package org.opentripplanner.ext.emissions; + +import jakarta.inject.Inject; +import java.util.Optional; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +@Sandbox +public class EmissionsDataService implements EmissionsService { + + private EmissionsDataModel emissionsDataModel; + + public EmissionsDataService() {} + + @Inject + public EmissionsDataService(EmissionsDataModel emissionsDataModel) { + this.emissionsDataModel = emissionsDataModel; + } + + @Override + public Optional getEmissionsPerMeterForRoute( + FeedScopedId feedScopedRouteId, + EmissionType emissionType + ) { + switch (emissionType) { + case CO2: + return this.emissionsDataModel.getCO2EmissionsById(feedScopedRouteId); + default: + return Optional.empty(); + } + } + + @Override + public Optional getCarEmissionsPerMeter(EmissionType emissionType) { + switch (emissionType) { + case CO2: + return Optional.ofNullable(this.emissionsDataModel.getCarAvgCo2PerMeter()); + default: + return Optional.empty(); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java new file mode 100644 index 00000000000..82f42efd6fb --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java @@ -0,0 +1,95 @@ +package org.opentripplanner.ext.emissions; + +import java.util.List; +import java.util.Optional; +import org.opentripplanner.ext.flex.FlexibleTransitLeg; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.model.plan.Emissions; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.ScheduledTransitLeg; +import org.opentripplanner.model.plan.StreetLeg; +import org.opentripplanner.model.plan.TransitLeg; +import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; +import org.opentripplanner.street.search.TraverseMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +@Sandbox +public record EmissionsFilter(EmissionsService emissionsService) implements ItineraryListFilter { + @Override + public List filter(List itineraries) { + for (Itinerary i : itineraries) { + Emissions emissions = new Emissions(); + + Optional carbonDioxide = this.getEmissionsForItinerary(i, EmissionType.CO2); + if (carbonDioxide.isPresent()) { + emissions.setCo2grams(carbonDioxide.get()); + } + + i.setEmissions(emissions); + } + return itineraries; + } + + public Optional getEmissionsForItinerary(Itinerary itinerary, EmissionType emissionType) { + List transitLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg) + .map(TransitLeg.class::cast) + .toList(); + + if (!transitLegs.isEmpty()) { + return Optional.ofNullable(calculateEmissionsForTransit(transitLegs, emissionType)); + } + + List carLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof StreetLeg) + .map(StreetLeg.class::cast) + .filter(leg -> leg.getMode() == TraverseMode.CAR) + .toList(); + + if (!carLegs.isEmpty()) { + return calculateEmissionsForCar(carLegs, emissionType); + } + return Optional.empty(); + } + + private Double calculateEmissionsForTransit( + List transitLegs, + EmissionType emissionType + ) { + Double emissions = 0.0; + for (TransitLeg leg : transitLegs) { + FeedScopedId feedScopedRouteId = new FeedScopedId( + leg.getAgency().getId().getFeedId(), + leg.getRoute().getId().getId() + ); + Optional emissionsForRoute = emissionsService.getEmissionsPerMeterForRoute( + feedScopedRouteId, + emissionType + ); + if (emissionsForRoute.isPresent()) { + emissions += emissionsForRoute.get() * leg.getDistanceMeters(); + } else { + // Partial results would not give an accurate representation of the emissions. + return null; + } + } + return emissions; + } + + private Optional calculateEmissionsForCar( + List carLegs, + EmissionType emissionType + ) { + Optional emissionsForCar = emissionsService.getCarEmissionsPerMeter(emissionType); + if (emissionsForCar.isPresent()) { + return Optional.of( + carLegs.stream().mapToDouble(leg -> emissionsForCar.get() * leg.getDistanceMeters()).sum() + ); + } + return Optional.empty(); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java similarity index 78% rename from src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java rename to src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java index e1f1fd091b4..b85aca535ca 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/DigitransitEmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.digitransitemissions; +package org.opentripplanner.ext.emissions; import com.csvreader.CsvReader; import dagger.Module; @@ -21,16 +21,16 @@ import org.slf4j.LoggerFactory; @Module -public class DigitransitEmissionsModule implements GraphBuilderModule { +public class EmissionsModule implements GraphBuilderModule { - private static final Logger LOG = LoggerFactory.getLogger(DigitransitEmissionsModule.class); + private static final Logger LOG = LoggerFactory.getLogger(EmissionsModule.class); private BuildConfig config; private EmissionsDataModel emissionsDataModel; private GraphBuilderDataSources dataSources; - private Map emissionsData = new HashMap<>(); + private Map emissionsData = new HashMap<>(); @Inject - public DigitransitEmissionsModule( + public EmissionsModule( GraphBuilderDataSources dataSources, BuildConfig config, EmissionsDataModel emissionsDataModel @@ -41,11 +41,11 @@ public DigitransitEmissionsModule( } public void buildGraph() { - if (config.digitransitEmissions != null) { + if (config.emissions != null) { LOG.info("Start emissions building!"); - double carAvgCo2PerKm = config.digitransitEmissions.getCarAvgCo2PerKm(); - double carAvgOccupancy = config.digitransitEmissions.getCarAvgOccupancy(); + double carAvgCo2PerKm = config.emissions.getCarAvgCo2PerKm(); + double carAvgOccupancy = config.emissions.getCarAvgOccupancy(); double carAvgEmissionsPerMeter = carAvgCo2PerKm / 1000 / carAvgOccupancy; for (ConfiguredDataSource gtfsData : dataSources.getGtfsConfiguredDatasource()) { @@ -55,8 +55,8 @@ public void buildGraph() { readGtfs(gtfsData.dataSource().path()); } } - this.emissionsDataModel.setEmissions(this.emissionsData); - this.emissionsDataModel.setCarAvgCo2EmissionsPerMeter(carAvgEmissionsPerMeter); + this.emissionsDataModel.setCo2Emissions(this.emissionsData); + this.emissionsDataModel.setCarAvgCo2PerMeter(carAvgEmissionsPerMeter); } } @@ -96,7 +96,7 @@ private void readEmissions(InputStream stream, String feedId) throws IOException Double avgPassengerCount = Double.parseDouble(reader.get("avg_passenger_count")); this.emissionsData.put( new FeedScopedId(feedId, routeId), - DigitransitEmissions.newDigitransitEmissions(avgCo2PerVehiclePerMeter, avgPassengerCount) + calculateEmissionsPerPassengerPerMeter(avgCo2PerVehiclePerMeter, avgPassengerCount) ); } } @@ -112,4 +112,14 @@ private String readFeedId(InputStream stream) { throw new RuntimeException(e); } } + + private static double calculateEmissionsPerPassengerPerMeter( + double avgCo2PerVehiclePerMeter, + double avgPassengerCount + ) { + if (avgPassengerCount <= 1) { + return avgCo2PerVehiclePerMeter; + } + return avgCo2PerVehiclePerMeter / avgPassengerCount; + } } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java new file mode 100644 index 00000000000..3731209742e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java @@ -0,0 +1,15 @@ +package org.opentripplanner.ext.emissions; + +import java.util.Optional; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +@Sandbox +public interface EmissionsService { + Optional getEmissionsPerMeterForRoute( + FeedScopedId feedScopedRouteId, + EmissionType emissionType + ); + + Optional getCarEmissionsPerMeter(EmissionType emissionType); +} diff --git a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsServiceModule.java similarity index 78% rename from src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java rename to src/ext/java/org/opentripplanner/ext/emissions/EmissionsServiceModule.java index 76f17320792..256a3024c4a 100644 --- a/src/ext/java/org/opentripplanner/ext/digitransitemissions/EmissionsServiceModule.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsServiceModule.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.digitransitemissions; +package org.opentripplanner.ext.emissions; import dagger.Module; import dagger.Provides; @@ -14,6 +14,6 @@ public class EmissionsServiceModule { @Provides @Singleton public EmissionsService provideEmissionsService(EmissionsDataModel emissionsDataModel) { - return new DigitransitEmissionsService(emissionsDataModel); + return new EmissionsDataService(emissionsDataModel); } } diff --git a/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java b/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java new file mode 100644 index 00000000000..7bc25b7519f --- /dev/null +++ b/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java @@ -0,0 +1,14 @@ +package org.opentripplanner.api.mapping; + +import org.opentripplanner.api.model.ApiEmissions; +import org.opentripplanner.model.plan.Emissions; + +public class EmissionsMapper { + + public static ApiEmissions mapEmissions(Emissions emissions) { + if (emissions == null) { + return null; + } + return new ApiEmissions(emissions.getCo2grams()); + } +} diff --git a/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java b/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java index eca57e0d8d2..a271db8075a 100644 --- a/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java @@ -12,10 +12,12 @@ public class ItineraryMapper { private final LegMapper legMapper; private final FareMapper fareMapper; + private final EmissionsMapper emissionsMapper; public ItineraryMapper(Locale locale, boolean addIntermediateStops) { this.legMapper = new LegMapper(locale, addIntermediateStops); this.fareMapper = new FareMapper(locale); + this.emissionsMapper = new EmissionsMapper(); } public List mapItineraries(Collection domain) { @@ -45,7 +47,7 @@ public ApiItinerary mapItinerary(Itinerary domain) { api.tooSloped = domain.isTooSloped(); api.arrivedAtDestinationWithRentedBicycle = domain.isArrivedAtDestinationWithRentedVehicle(); api.fare = fareMapper.mapFare(domain); - api.emissions = domain.getEmissions(); + api.emissions = emissionsMapper.mapEmissions(domain.getEmissions()); api.legs = legMapper.mapLegs(domain.getLegs()); api.systemNotices = SystemNoticeMapper.mapSystemNotices(domain.getSystemNotices()); api.accessibilityScore = domain.getAccessibilityScore(); diff --git a/src/main/java/org/opentripplanner/api/model/ApiEmissions.java b/src/main/java/org/opentripplanner/api/model/ApiEmissions.java new file mode 100644 index 00000000000..85e84562112 --- /dev/null +++ b/src/main/java/org/opentripplanner/api/model/ApiEmissions.java @@ -0,0 +1,11 @@ +package org.opentripplanner.api.model; + +/** + * The emissions of an Itinerary + */ +public record ApiEmissions( + /** + * The carbon dioxide emissions of the itinerary in grams. + */ + Double co2grams +) {} diff --git a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java index 9a2aac6cfd8..3b1e743ce7d 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java +++ b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java @@ -81,9 +81,9 @@ public class ApiItinerary { public ApiItineraryFares fare = new ApiItineraryFares(Map.of(), Map.of(), null, null); /** - * CO₂ emissions of this trip + * The emissions of this trip. */ - public Double emissions; + public ApiEmissions emissions; /** * A list of Legs. Each Leg is either a walking (cycling, car) portion of the trip, or a transit * trip on a particular vehicle. So a trip where the use walks to the Q train, transfers to the 6, diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java index ae2267bc481..cd640a791b4 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java @@ -9,6 +9,7 @@ import org.opentripplanner.apis.gtfs.mapping.NumberMapper; import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.fare.ItineraryFares; +import org.opentripplanner.model.plan.Emissions; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; @@ -100,12 +101,21 @@ public DataFetcher accessibilityScore() { return environment -> NumberMapper.toDouble(getSource(environment).getAccessibilityScore()); } - private Itinerary getSource(DataFetchingEnvironment environment) { - return environment.getSource(); + @Override + public DataFetcher> emissions() { + return environment -> { + Emissions emissions = getSource(environment).getEmissions(); + if (emissions == null) { + return null; + } + + Map result = new HashMap<>(); + result.put("co2grams", emissions.getCo2grams()); + return result; + }; } - @Override - public DataFetcher emissions() { - return environment -> NumberMapper.toDouble(getSource(environment).getEmissions()); + private Itinerary getSource(DataFetchingEnvironment environment) { + return environment.getSource(); } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 21555ad7a92..9d349a390f5 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -390,7 +390,7 @@ public interface GraphQLItinerary { public DataFetcher elevationLost(); - public DataFetcher emissions(); + public DataFetcher> emissions(); public DataFetcher endTime(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index f4ddc902771..3593145e274 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1,7 +1,6 @@ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. package org.opentripplanner.apis.gtfs.generated; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 89b938be9eb..b8e7936cbc3 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -67,7 +67,7 @@ public enum OTPFeature { false, "Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads." ), - Co2Emissions(false, true, "Enable emissions calculation and data handling."), + Co2Emissions(false, true, "Enable the emissions sandbox module."), DataOverlay( false, true, diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index 9f87e01cb72..93945ce4985 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -9,7 +9,7 @@ import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.framework.lang.OtpNumberFormat; diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java index d3a8050ff4b..d22a300ef1f 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java @@ -7,8 +7,8 @@ import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.ext.dataoverlay.EdgeUpdaterModule; -import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsModule; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsModule; import org.opentripplanner.ext.flex.AreaStopsToVerticesMapper; import org.opentripplanner.ext.transferanalyzer.DirectTransferAnalyzer; import org.opentripplanner.graph_builder.GraphBuilder; @@ -39,7 +39,7 @@ public interface GraphBuilderFactory { GraphBuilder graphBuilder(); OsmModule osmModule(); GtfsModule gtfsModule(); - DigitransitEmissionsModule emissionsModule(); + EmissionsModule emissionsModule(); NetexModule netexModule(); TimeZoneAdjusterModule timeZoneAdjusterModule(); TripPatternNamer tripPatternNamer(); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 6bd372357fc..931d9885f32 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -11,8 +11,8 @@ import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.ext.dataoverlay.EdgeUpdaterModule; import org.opentripplanner.ext.dataoverlay.configure.DataOverlayFactory; -import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsModule; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsModule; import org.opentripplanner.ext.transferanalyzer.DirectTransferAnalyzer; import org.opentripplanner.graph_builder.ConfiguredDataSource; import org.opentripplanner.graph_builder.GraphBuilderDataSources; @@ -110,12 +110,12 @@ static GtfsModule provideGtfsModule( @Provides @Singleton - static DigitransitEmissionsModule provideEmissionsModule( + static EmissionsModule provideEmissionsModule( GraphBuilderDataSources dataSources, BuildConfig config, EmissionsDataModel emissionsDataModel ) { - return new DigitransitEmissionsModule(dataSources, config, emissionsDataModel); + return new EmissionsModule(dataSources, config, emissionsDataModel); } @Provides diff --git a/src/main/java/org/opentripplanner/model/plan/Emissions.java b/src/main/java/org/opentripplanner/model/plan/Emissions.java new file mode 100644 index 00000000000..8aaf9eaa9e1 --- /dev/null +++ b/src/main/java/org/opentripplanner/model/plan/Emissions.java @@ -0,0 +1,17 @@ +package org.opentripplanner.model.plan; + +import org.opentripplanner.framework.lang.Sandbox; + +@Sandbox +public class Emissions { + + private Double co2grams; + + public Double getCo2grams() { + return co2grams; + } + + public void setCo2grams(Double co2grams) { + this.co2grams = co2grams; + } +} diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index e824d715a8f..c4f7a488b01 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -51,7 +51,7 @@ public class Itinerary { /* Sandbox experimental properties */ private Float accessibilityScore; - private Double emissions; + private Emissions emissions; /* other properties */ @@ -590,14 +590,14 @@ public List getScheduledTransitLegs() { } /** - * The CO₂ emissions of this itinerary. + * The emissions of this itinerary. */ - public void setEmissions(Double emissions) { + public void setEmissions(Emissions emissions) { this.emissions = emissions; } @Nullable - public Double getEmissions() { + public Emissions getEmissions() { return this.emissions; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 4bd52e897f3..747e872710d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -3,7 +3,7 @@ import java.time.Instant; import java.util.List; import java.util.function.Consumer; -import org.opentripplanner.ext.digitransitemissions.EmissionsFilter; +import org.opentripplanner.ext.emissions.EmissionsFilter; import org.opentripplanner.ext.ridehailing.RideHailingFilter; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.model.plan.Itinerary; diff --git a/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java b/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java index dca188953f7..8b066b1d1d2 100644 --- a/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java +++ b/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java @@ -17,7 +17,7 @@ import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.datastore.api.DataSource; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.framework.geometry.CompactElevationProfile; import org.opentripplanner.framework.lang.OtpNumberFormat; diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index b853e4de583..4d69684f6d8 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -5,7 +5,7 @@ import java.util.Locale; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext; -import org.opentripplanner.ext.digitransitemissions.EmissionsService; +import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.ext.vectortiles.VectorTilesResource; import org.opentripplanner.framework.application.OTPFeature; diff --git a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index 2e802d541c3..060d224b487 100644 --- a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -18,7 +18,7 @@ import javax.annotation.Nonnull; import org.opentripplanner.datastore.api.OtpDataStoreConfig; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayConfig; -import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsConfig; +import org.opentripplanner.ext.emissions.EmissionsConfig; import org.opentripplanner.ext.fares.FaresConfiguration; import org.opentripplanner.framework.geometry.CompactElevationProfile; import org.opentripplanner.framework.lang.ObjectUtils; @@ -165,7 +165,7 @@ public class BuildConfig implements OtpDataStoreConfig { public final Set boardingLocationTags; public final DemExtractParametersList dem; public final OsmExtractParametersList osm; - public final DigitransitEmissionsConfig digitransitEmissions; + public final EmissionsConfig emissions; public final TransitFeeds transitFeeds; public boolean staticParkAndRide; public boolean staticBikeParkAndRide; @@ -613,7 +613,7 @@ that we support remote input files (cloud storage or arbitrary URLs) not all dat osm = OsmConfig.mapOsmConfig(root, "osm", osmDefaults); demDefaults = DemConfig.mapDemDefaultsConfig(root, "demDefaults"); dem = DemConfig.mapDemConfig(root, "dem", demDefaults); - digitransitEmissions = new DigitransitEmissionsConfig("digitransitEmissions", root); + emissions = new EmissionsConfig("emissions", root); netexDefaults = NetexConfig.mapNetexDefaultParameters(root, "netexDefaults"); gtfsDefaults = GtfsConfig.mapGtfsDefaultParameters(root, "gtfsDefaults"); diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 2845b2b974c..81e041f8407 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -3,7 +3,7 @@ import jakarta.ws.rs.core.Application; import javax.annotation.Nullable; import org.opentripplanner.datastore.api.DataSource; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.transmodelapi.TransmodelAPI; import org.opentripplanner.framework.application.LogMDCSupport; diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index c58477afbed..a550b0d5ff0 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -4,8 +4,8 @@ import dagger.Component; import jakarta.inject.Singleton; import javax.annotation.Nullable; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; -import org.opentripplanner.ext.digitransitemissions.EmissionsServiceModule; +import org.opentripplanner.ext.emissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsServiceModule; import org.opentripplanner.ext.ridehailing.configure.RideHailingServicesModule; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.raptor.configure.RaptorConfig; diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index d2b767e1f7a..2361be1cc38 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -6,7 +6,7 @@ import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.astar.spi.TraverseVisitor; -import org.opentripplanner.ext.digitransitemissions.EmissionsService; +import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; diff --git a/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java b/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java index a99065704ba..c8936ab3054 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java @@ -1,7 +1,7 @@ package org.opentripplanner.standalone.configure; import org.opentripplanner.datastore.api.DataSource; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.graph_builder.GraphBuilderDataSources; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.routing.graph.Graph; diff --git a/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java index 3562163e59d..fbd5ad2de51 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java @@ -6,7 +6,7 @@ import org.opentripplanner.datastore.OtpDataStore; import org.opentripplanner.datastore.configure.DataStoreModule; import org.opentripplanner.ext.datastore.gs.GsDataSourceModule; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.graph_builder.GraphBuilderDataSources; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 8ae6de8dc0c..79969e1a8c7 100644 --- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -5,7 +5,7 @@ import java.util.Locale; import javax.annotation.Nullable; import org.opentripplanner.astar.spi.TraverseVisitor; -import org.opentripplanner.ext.digitransitemissions.EmissionsService; +import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.ext.vectortiles.VectorTilesResource; import org.opentripplanner.inspector.raster.TileRendererManager; diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index b6da5570a19..97dc8a09552 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -900,6 +900,13 @@ type elevationProfileComponent { elevation: Float } +type Emissions { + """ + CO₂ emissions of the trip on this itinerary, in grams per person. + """ + co2grams: Float +} + type fare { type: String @deprecated @@ -1467,8 +1474,8 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float - """CO₂ emissions of the trip on this itinerary, in grams per person.""" - emissions: Float + """Emissions of the trip on this itinerary.""" + emissions: Emissions """ A list of Legs. Each Leg is either a walking (cycling, car) portion of the diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index 7e0560018d8..43573dd2621 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -4,9 +4,8 @@ import io.micrometer.core.instrument.Metrics; import java.util.List; -import org.opentripplanner.ext.digitransitemissions.DigitransitEmissionsService; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; -import org.opentripplanner.ext.digitransitemissions.EmissionsService; +import org.opentripplanner.ext.emissions.EmissionsDataService; +import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.vehiclepositions.VehiclePositionService; @@ -67,6 +66,6 @@ public static VehicleRentalService createVehicleRentalService() { } public static EmissionsService createEmissionsService() { - return new DigitransitEmissionsService(); + return new EmissionsDataService(); } } diff --git a/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java b/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java index 493e3bd3d16..0b4a7456e52 100644 --- a/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java +++ b/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java @@ -19,7 +19,7 @@ import org.opentripplanner.TestOtpModel; import org.opentripplanner.datastore.api.FileType; import org.opentripplanner.datastore.file.FileDataSource; -import org.opentripplanner.ext.digitransitemissions.EmissionsDataModel; +import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.framework.geometry.HashGridSpatialIndex; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; diff --git a/src/test/resources/standalone/config/build-config.json b/src/test/resources/standalone/config/build-config.json index 0bd6890812c..fb8c682bbda 100644 --- a/src/test/resources/standalone/config/build-config.json +++ b/src/test/resources/standalone/config/build-config.json @@ -74,7 +74,7 @@ } } ], - "digitransitEmissions": { + "emissions": { "carAvgCo2PerKm": 170, "carAvgOccupancy": 1.3 } From c0f4bd83000a34b3ec2a8259f7f3bb0bc559accd Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:25:56 +0300 Subject: [PATCH 29/68] Update src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls Co-authored-by: Joel Lappalainen --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 97dc8a09552..c513ee7e7ad 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -902,7 +902,7 @@ type elevationProfileComponent { type Emissions { """ - CO₂ emissions of the trip on this itinerary, in grams per person. + CO₂ emissions in grams. """ co2grams: Float } From 8afd6283396725b5ef19ffa8adfd04dcd95b5d1a Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 19 Oct 2023 14:28:31 +0300 Subject: [PATCH 30/68] Emissions naming fixed, emissions use record --- .../ext/emissions/EmissionsFilter.java | 4 ++-- .../api/mapping/EmissionsMapper.java | 2 +- .../api/mapping/ItineraryMapper.java | 2 +- .../org/opentripplanner/api/model/ApiEmissions.java | 2 +- .../org/opentripplanner/api/model/ApiItinerary.java | 2 +- .../apis/gtfs/datafetchers/ItineraryImpl.java | 13 ++----------- .../apis/gtfs/generated/GraphQLDataFetchers.java | 3 ++- .../apis/gtfs/generated/graphql-codegen.yml | 2 ++ .../org/opentripplanner/model/plan/Emissions.java | 10 +++++----- .../org/opentripplanner/model/plan/Itinerary.java | 12 ++++++------ .../java/org/opentripplanner/model/plan/Leg.java | 5 ----- .../org/opentripplanner/apis/gtfs/schema.graphqls | 6 +++--- 12 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java index 82f42efd6fb..1388a207e05 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java @@ -22,10 +22,10 @@ public List filter(List itineraries) { Optional carbonDioxide = this.getEmissionsForItinerary(i, EmissionType.CO2); if (carbonDioxide.isPresent()) { - emissions.setCo2grams(carbonDioxide.get()); + emissions.setCo2Grams(carbonDioxide.get()); } - i.setEmissions(emissions); + i.setEmissionsPerPerson(emissions); } return itineraries; } diff --git a/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java b/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java index 7bc25b7519f..5101ac32244 100644 --- a/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java @@ -9,6 +9,6 @@ public static ApiEmissions mapEmissions(Emissions emissions) { if (emissions == null) { return null; } - return new ApiEmissions(emissions.getCo2grams()); + return new ApiEmissions(emissions.getCo2Grams()); } } diff --git a/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java b/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java index a271db8075a..08036a5cb5d 100644 --- a/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java @@ -47,7 +47,7 @@ public ApiItinerary mapItinerary(Itinerary domain) { api.tooSloped = domain.isTooSloped(); api.arrivedAtDestinationWithRentedBicycle = domain.isArrivedAtDestinationWithRentedVehicle(); api.fare = fareMapper.mapFare(domain); - api.emissions = emissionsMapper.mapEmissions(domain.getEmissions()); + api.emissionsPerPerson = emissionsMapper.mapEmissions(domain.getEmissionsPerPerson()); api.legs = legMapper.mapLegs(domain.getLegs()); api.systemNotices = SystemNoticeMapper.mapSystemNotices(domain.getSystemNotices()); api.accessibilityScore = domain.getAccessibilityScore(); diff --git a/src/main/java/org/opentripplanner/api/model/ApiEmissions.java b/src/main/java/org/opentripplanner/api/model/ApiEmissions.java index 85e84562112..cf7e6c3cdf2 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiEmissions.java +++ b/src/main/java/org/opentripplanner/api/model/ApiEmissions.java @@ -7,5 +7,5 @@ public record ApiEmissions( /** * The carbon dioxide emissions of the itinerary in grams. */ - Double co2grams + Double co2Grams ) {} diff --git a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java index 3b1e743ce7d..4df351e6e5e 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java +++ b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java @@ -83,7 +83,7 @@ public class ApiItinerary { /** * The emissions of this trip. */ - public ApiEmissions emissions; + public ApiEmissions emissionsPerPerson; /** * A list of Legs. Each Leg is either a walking (cycling, car) portion of the trip, or a transit * trip on a particular vehicle. So a trip where the use walks to the Q train, transfers to the 6, diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java index cd640a791b4..e446be9202c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java @@ -102,17 +102,8 @@ public DataFetcher accessibilityScore() { } @Override - public DataFetcher> emissions() { - return environment -> { - Emissions emissions = getSource(environment).getEmissions(); - if (emissions == null) { - return null; - } - - Map result = new HashMap<>(); - result.put("co2grams", emissions.getCo2grams()); - return result; - }; + public DataFetcher emissionsPerPerson() { + return environment -> getSource(environment).getEmissionsPerPerson(); } private Itinerary getSource(DataFetchingEnvironment environment) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 9d349a390f5..b5f45ed35b0 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -31,6 +31,7 @@ import org.opentripplanner.model.fare.FareProduct; import org.opentripplanner.model.fare.FareProductUse; import org.opentripplanner.model.fare.RiderCategory; +import org.opentripplanner.model.plan.Emissions; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.StopArrival; @@ -390,7 +391,7 @@ public interface GraphQLItinerary { public DataFetcher elevationLost(); - public DataFetcher> emissions(); + public DataFetcher emissionsPerPerson(); public DataFetcher endTime(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index 14d00ec1bd4..2265b364b27 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -50,6 +50,7 @@ config: debugOutput: org.opentripplanner.api.resource.DebugOutput#DebugOutput DepartureRow: org.opentripplanner.routing.graphfinder.PatternAtStop#PatternAtStop elevationProfileComponent: org.opentripplanner.model.plan.ElevationProfile.Step + Emissions: org.opentripplanner.model.plan.Emissions#Emissions fare: java.util.Map#Map fareComponent: org.opentripplanner.routing.core.FareComponent#FareComponent Feed: String @@ -107,3 +108,4 @@ config: FareMedium: org.opentripplanner.model.fare.FareMedium#FareMedium RiderCategory: org.opentripplanner.model.fare.RiderCategory#RiderCategory StopPosition: org.opentripplanner.apis.gtfs.model.StopPosition#StopPosition + diff --git a/src/main/java/org/opentripplanner/model/plan/Emissions.java b/src/main/java/org/opentripplanner/model/plan/Emissions.java index 8aaf9eaa9e1..3f2e8ed18ec 100644 --- a/src/main/java/org/opentripplanner/model/plan/Emissions.java +++ b/src/main/java/org/opentripplanner/model/plan/Emissions.java @@ -5,13 +5,13 @@ @Sandbox public class Emissions { - private Double co2grams; + private Double co2Grams; - public Double getCo2grams() { - return co2grams; + public Double getCo2Grams() { + return co2Grams; } - public void setCo2grams(Double co2grams) { - this.co2grams = co2grams; + public void setCo2Grams(Double co2Grams) { + this.co2Grams = co2Grams; } } diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index c4f7a488b01..65f488d81e4 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -51,7 +51,7 @@ public class Itinerary { /* Sandbox experimental properties */ private Float accessibilityScore; - private Emissions emissions; + private Emissions emissionsPerPerson; /* other properties */ @@ -251,7 +251,7 @@ public String toString() { .addNum("elevationGained", elevationGained, 0.0) .addCol("legs", legs) .addObj("fare", fare) - .addObj("emissions", emissions) + .addObj("emissions", emissionsPerPerson) .toString(); } @@ -592,12 +592,12 @@ public List getScheduledTransitLegs() { /** * The emissions of this itinerary. */ - public void setEmissions(Emissions emissions) { - this.emissions = emissions; + public void setEmissionsPerPerson(Emissions emissionsPerPerson) { + this.emissionsPerPerson = emissionsPerPerson; } @Nullable - public Emissions getEmissions() { - return this.emissions; + public Emissions getEmissionsPerPerson() { + return this.emissionsPerPerson; } } diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java index 7c8fbb03c1a..aa9949dae24 100644 --- a/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -411,11 +411,6 @@ default Float accessibilityScore() { return null; } - @Nullable - default Double emissions() { - return null; - } - default Boolean getRentedVehicle() { return null; } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index c513ee7e7ad..4448cc85061 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -904,7 +904,7 @@ type Emissions { """ CO₂ emissions in grams. """ - co2grams: Float + co2Grams: Float } type fare { @@ -1474,8 +1474,8 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float - """Emissions of the trip on this itinerary.""" - emissions: Emissions + """Emissions of this itinerary per traveler.""" + emissionsPerPerson: Emissions """ A list of Legs. Each Leg is either a walking (cycling, car) portion of the From 0d2634fed0d31ba2d8bf44ad7d48c0fe4e2a8e15 Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 24 Oct 2023 13:50:47 +0300 Subject: [PATCH 31/68] Emissions grams scalar, refactoring, documentation, tests --- doc-templates/Emissions.md | 54 +++++++++ docs/apis/GraphQL-Tutorial.md | 3 + docs/sandbox/Emissions.md | 35 ++++-- .../emissions/Co2EmissionsDataReaderTest.java | 43 +++++++ .../ext/emissions/Co2EmissionsDataReader.java | 113 ++++++++++++++++++ .../ext/emissions/EmissionsDataService.java | 2 +- .../ext/emissions/EmissionsFilter.java | 5 +- .../ext/emissions/EmissionsModule.java | 69 +---------- .../ext/emissions/EmissionsService.java | 16 ++- .../api/mapping/EmissionsMapper.java | 2 +- .../api/model/ApiEmissions.java | 4 +- .../apis/gtfs/GraphQLScalars.java | 36 ++++++ .../apis/gtfs/GtfsGraphQLIndex.java | 1 + .../apis/gtfs/generated/graphql-codegen.yml | 1 + .../framework/model/Grams.java | 28 +++++ .../opentripplanner/model/plan/Emissions.java | 11 +- .../opentripplanner/apis/gtfs/schema.graphqls | 4 +- .../apis/gtfs/GraphQLIntegrationTest.java | 6 + .../doc/EmissionsConfigurationDocTest.java | 76 ++++++++++++ .../ItineraryListFilterChainTest.java | 38 ++++++ .../emissions-invalid-test-gtfs/emissions.txt | 4 + .../emissions-invalid-test-gtfs/feed_info.txt | 2 + .../resources/gtfs/emissions-test-gtfs.zip | Bin 0 -> 595 bytes .../gtfs/emissions-test-gtfs/emissions.txt | 7 ++ .../gtfs/emissions-test-gtfs/feed_info.txt | 2 + .../apis/gtfs/expectations/plan.json | 3 + .../apis/gtfs/queries/plan.graphql | 3 + 27 files changed, 481 insertions(+), 87 deletions(-) create mode 100644 doc-templates/Emissions.md create mode 100644 src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java create mode 100644 src/main/java/org/opentripplanner/framework/model/Grams.java create mode 100644 src/test/java/org/opentripplanner/generate/doc/EmissionsConfigurationDocTest.java create mode 100644 src/test/resources/gtfs/emissions-invalid-test-gtfs/emissions.txt create mode 100644 src/test/resources/gtfs/emissions-invalid-test-gtfs/feed_info.txt create mode 100644 src/test/resources/gtfs/emissions-test-gtfs.zip create mode 100644 src/test/resources/gtfs/emissions-test-gtfs/emissions.txt create mode 100644 src/test/resources/gtfs/emissions-test-gtfs/feed_info.txt diff --git a/doc-templates/Emissions.md b/doc-templates/Emissions.md new file mode 100644 index 00000000000..81ea0d968e2 --- /dev/null +++ b/doc-templates/Emissions.md @@ -0,0 +1,54 @@ +# CO₂ Emissions calculation + +## Contact Info + +- Digitransit Team + +## Documentation + +Graph build import of CO₂ Emissions from GTFS data sets (through custom emissions.txt extension) +and the ability to attach them to itineraries by Digitransit team. +The emissions are represented in grams per kilometer (g/Km) unit. + +Emissions data is located in an emissions.txt file within a gtfs package and has the following columns: + +`route_id`: route id + +`avg_co2_per_vehicle_per_km`: Average carbon dioxide equivalent value for the vehicles used on the route at grams/Km units. + +`avg_passenger_count`: Average passenger count for the vehicles on the route. + +For example: +```csv +route_id,avg_co2_per_vehicle_per_km,avg_passenger_count +1234,123,20 +2345,0,0 +3456,12.3,20.0 +``` + +Emissions data is loaded from the gtfs package and embedded into the graph during the build process. + + +### Configuration +To enable this functionality, you need to enable the "Co2Emissions" feature in the +`otp-config.json` file. + +```JSON +//otp-config.json +{ + "Co2Emissions": true +} + +``` +Include the `emissions` object in the +`build-config.json` file. The `emissions` object should contain parameters called +`carAvgCo2PerKm` and `carAvgOccupancy`. The `carAvgCo2PerKm` provides the average emissions value for a car in g/km and +the `carAvgOccupancy` provides the average number of passengers in a car. + + + +## Changelog + +### OTP 2.5 + +- Initial implementation of the emissions calculation. diff --git a/docs/apis/GraphQL-Tutorial.md b/docs/apis/GraphQL-Tutorial.md index 8df6d6f38ee..1f849f392c7 100644 --- a/docs/apis/GraphQL-Tutorial.md +++ b/docs/apis/GraphQL-Tutorial.md @@ -90,6 +90,9 @@ Most people want to get routing results out of OTP, so lets see the query for th itineraries { startTime endTime + emissionsPerPerson { + co2 + } legs { mode startTime diff --git a/docs/sandbox/Emissions.md b/docs/sandbox/Emissions.md index f0a135eff10..280d4aeacdb 100644 --- a/docs/sandbox/Emissions.md +++ b/docs/sandbox/Emissions.md @@ -31,32 +31,49 @@ Emissions data is loaded from the gtfs package and embedded into the graph durin ### Configuration To enable this functionality, you need to enable the "Co2Emissions" feature in the -`otp-config.json` file. +`otp-config.json` file. -```json +```JSON //otp-config.json { "Co2Emissions": true } + ``` Include the `emissions` object in the `build-config.json` file. The `emissions` object should contain parameters called `carAvgCo2PerKm` and `carAvgOccupancy`. The `carAvgCo2PerKm` provides the average emissions value for a car in g/km and the `carAvgOccupancy` provides the average number of passengers in a car. -```json -//build-config.json + + + +### Example configuration + +```JSON +// build-config.json { - "emissions": { - "carAvgCo2PerKm": 170, - "carAvgOccupancy": 1.3 + "emissions" : { + "carAvgCo2PerKm" : 170, + "carAvgOccupancy" : 1.3 } } ``` +### Overview + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|------------------|:---------:|------------------------------------------------------------|:----------:|---------------|:-----:| +| carAvgCo2PerKm | `integer` | The average CO₂ emissions of a car in grams per kilometer. | *Optional* | `170` | na | +| carAvgOccupancy | `double` | The average number of passengers in a car. | *Optional* | `1.3` | na | + + +### Details + + + + ## Changelog ### OTP 2.5 - Initial implementation of the emissions calculation. - - diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java new file mode 100644 index 00000000000..ae9df710c91 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java @@ -0,0 +1,43 @@ +package org.opentripplanner.ext.emissions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +public class Co2EmissionsDataReaderTest { + + private static final String CO2_GTFS_ZIP_PATH = "src/test/resources/gtfs/emissions-test-gtfs.zip"; + private static final String CO2_GTFS_PATH = "src/test/resources/gtfs/emissions-test-gtfs"; + private static final String INVALID_CO2_GTFS_PATH = + "src/test/resources/gtfs/emissions-invalid-test-gtfs"; + + private Co2EmissionsDataReader co2EmissionsDataReader = new Co2EmissionsDataReader(); + private Map emissions; + + @BeforeEach + void SetUp() { + this.emissions = new HashMap<>(); + } + + @Test + void testCo2EmissionsZipDataReading() { + this.emissions = co2EmissionsDataReader.readGtfsZip(CO2_GTFS_ZIP_PATH); + assertEquals(6, emissions.size()); + } + + @Test + void testCo2EmissionsDataReading() { + this.emissions = co2EmissionsDataReader.readGtfs(CO2_GTFS_PATH); + assertEquals(6, emissions.size()); + } + + @Test + void testInvalidCo2EmissionsDataReading() { + this.emissions = co2EmissionsDataReader.readGtfs(INVALID_CO2_GTFS_PATH); + assertEquals(0, emissions.size()); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java new file mode 100644 index 00000000000..a63f8c20964 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java @@ -0,0 +1,113 @@ +package org.opentripplanner.ext.emissions; + +import com.csvreader.CsvReader; +import com.esotericsoftware.minlog.Log; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.zip.ZipFile; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Co2EmissionsDataReader { + + private static final Logger LOG = LoggerFactory.getLogger(Co2EmissionsDataReader.class); + private Map emissionsData = new HashMap<>(); + + public Map readGtfs(String filePath) { + try { + InputStream feedInfoStream = new FileInputStream(filePath + "/feed_info.txt"); + String feedId = readFeedId(feedInfoStream); + feedInfoStream.close(); + + InputStream stream = new FileInputStream(filePath + "/emissions.txt"); + readEmissions(stream, feedId); + stream.close(); + return this.emissionsData; + } catch (IOException e) { + LOG.error("Reading emissions data failed.", e); + } + return null; + } + + public Map readGtfsZip(String filePath) { + try { + ZipFile zipFile = new ZipFile(new File(filePath), ZipFile.OPEN_READ); + String feedId = readFeedId(zipFile.getInputStream(zipFile.getEntry("feed_info.txt"))); + InputStream stream = zipFile.getInputStream(zipFile.getEntry("emissions.txt")); + readEmissions(stream, feedId); + zipFile.close(); + return this.emissionsData; + } catch (IOException e) { + LOG.error("Reading emissions data failed.", e); + } + return null; + } + + private void readEmissions(InputStream stream, String feedId) throws IOException { + CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); + reader.readHeaders(); + + while (reader.readRecord()) { + String routeId = reader.get("route_id"); + String avgCo2PerVehiclePerKmString = reader.get("avg_co2_per_vehicle_per_km"); + String avgPassengerCountString = reader.get("avg_passenger_count"); + + if (avgCo2PerVehiclePerKmString.isEmpty()) { + LOG.error("Value for avg_co2_per_vehicle_per_km is missing in the Emissions.txt"); + } + if (avgPassengerCountString.isEmpty()) { + LOG.error("Value for avg_passenger_count is missing in the Emissions.txt"); + } + + Double avgCo2PerVehiclePerMeter = Double.parseDouble(avgCo2PerVehiclePerKmString) / 1000; + Double avgPassengerCount = Double.parseDouble(reader.get("avg_passenger_count")); + Optional emissions = calculateEmissionsPerPassengerPerMeter( + routeId, + avgCo2PerVehiclePerMeter, + avgPassengerCount + ); + if (emissions.isPresent()) { + this.emissionsData.put(new FeedScopedId(feedId, routeId), emissions.get()); + } + } + } + + private String readFeedId(InputStream stream) { + try { + CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); + reader.readHeaders(); + reader.readRecord(); + return reader.get("feed_id"); + } catch (IOException e) { + LOG.error("Reading feed id for emissions failed.", e); + throw new RuntimeException(e); + } + } + + private static Optional calculateEmissionsPerPassengerPerMeter( + String routeId, + double avgCo2PerVehiclePerMeter, + double avgPassengerCount + ) { + if (avgCo2PerVehiclePerMeter == 0) { + // Passenger number is irrelevant when emissions is 0. + return Optional.of(avgCo2PerVehiclePerMeter); + } + if (avgPassengerCount <= 0 || avgCo2PerVehiclePerMeter < 0) { + Log.error( + "Invalid data for route " + + routeId + + ": avgPassengerCount is 0 or less, but avgCo2PerVehiclePerMeter is nonzero or avgCo2PerVehiclePerMeter is negative." + ); + return Optional.empty(); + } + return Optional.of(avgCo2PerVehiclePerMeter / avgPassengerCount); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java index 53b7f4455d4..74ea79834c2 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java @@ -31,7 +31,7 @@ public Optional getEmissionsPerMeterForRoute( } @Override - public Optional getCarEmissionsPerMeter(EmissionType emissionType) { + public Optional getEmissionsPerMeterForCar(EmissionType emissionType) { switch (emissionType) { case CO2: return Optional.ofNullable(this.emissionsDataModel.getCarAvgCo2PerMeter()); diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java index 1388a207e05..94cdb397701 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java @@ -4,6 +4,7 @@ import java.util.Optional; import org.opentripplanner.ext.flex.FlexibleTransitLeg; import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.framework.model.Grams; import org.opentripplanner.model.plan.Emissions; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.ScheduledTransitLeg; @@ -22,7 +23,7 @@ public List filter(List itineraries) { Optional carbonDioxide = this.getEmissionsForItinerary(i, EmissionType.CO2); if (carbonDioxide.isPresent()) { - emissions.setCo2Grams(carbonDioxide.get()); + emissions.setCo2(new Grams(carbonDioxide.get())); } i.setEmissionsPerPerson(emissions); @@ -84,7 +85,7 @@ private Optional calculateEmissionsForCar( List carLegs, EmissionType emissionType ) { - Optional emissionsForCar = emissionsService.getCarEmissionsPerMeter(emissionType); + Optional emissionsForCar = emissionsService.getEmissionsPerMeterForCar(emissionType); if (emissionsForCar.isPresent()) { return Optional.of( carLegs.stream().mapToDouble(leg -> emissionsForCar.get() * leg.getDistanceMeters()).sum() diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java index b85aca535ca..e46fedd7431 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java @@ -43,83 +43,20 @@ public EmissionsModule( public void buildGraph() { if (config.emissions != null) { LOG.info("Start emissions building!"); - + Co2EmissionsDataReader co2EmissionsDataReader = new Co2EmissionsDataReader(); double carAvgCo2PerKm = config.emissions.getCarAvgCo2PerKm(); double carAvgOccupancy = config.emissions.getCarAvgOccupancy(); double carAvgEmissionsPerMeter = carAvgCo2PerKm / 1000 / carAvgOccupancy; for (ConfiguredDataSource gtfsData : dataSources.getGtfsConfiguredDatasource()) { if (gtfsData.dataSource().name().contains(".zip")) { - readGtfsZip(gtfsData.dataSource().path()); + emissionsData = co2EmissionsDataReader.readGtfsZip(gtfsData.dataSource().path()); } else { - readGtfs(gtfsData.dataSource().path()); + emissionsData = co2EmissionsDataReader.readGtfs(gtfsData.dataSource().path()); } } this.emissionsDataModel.setCo2Emissions(this.emissionsData); this.emissionsDataModel.setCarAvgCo2PerMeter(carAvgEmissionsPerMeter); } } - - private void readGtfs(String filePath) { - try { - InputStream feedInfoStream = new FileInputStream(filePath + "/feed_info.txt"); - String feedId = readFeedId(feedInfoStream); - feedInfoStream.close(); - - InputStream stream = new FileInputStream(filePath + "/emissions.txt"); - readEmissions(stream, feedId); - stream.close(); - } catch (IOException e) { - LOG.error("Reading emissions data failed.", e); - } - } - - private void readGtfsZip(String filePath) { - try { - ZipFile zipFile = new ZipFile(new File(filePath), ZipFile.OPEN_READ); - String feedId = readFeedId(zipFile.getInputStream(zipFile.getEntry("feed_info.txt"))); - InputStream stream = zipFile.getInputStream(zipFile.getEntry("emissions.txt")); - readEmissions(stream, feedId); - zipFile.close(); - } catch (IOException e) { - LOG.error("Reading emissions data failed.", e); - } - } - - private void readEmissions(InputStream stream, String feedId) throws IOException { - CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); - reader.readHeaders(); - while (reader.readRecord()) { - String routeId = reader.get("route_id"); - Double avgCo2PerVehiclePerMeter = - Double.parseDouble(reader.get("avg_co2_per_vehicle_per_km")) / 1000; - Double avgPassengerCount = Double.parseDouble(reader.get("avg_passenger_count")); - this.emissionsData.put( - new FeedScopedId(feedId, routeId), - calculateEmissionsPerPassengerPerMeter(avgCo2PerVehiclePerMeter, avgPassengerCount) - ); - } - } - - private String readFeedId(InputStream stream) { - try { - CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); - reader.readHeaders(); - reader.readRecord(); - return reader.get("feed_id"); - } catch (IOException e) { - LOG.error("Reading feed id for emissions failed.", e); - throw new RuntimeException(e); - } - } - - private static double calculateEmissionsPerPassengerPerMeter( - double avgCo2PerVehiclePerMeter, - double avgPassengerCount - ) { - if (avgPassengerCount <= 1) { - return avgCo2PerVehiclePerMeter; - } - return avgCo2PerVehiclePerMeter / avgPassengerCount; - } } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java index 3731209742e..a6e9372a415 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java @@ -4,12 +4,26 @@ import org.opentripplanner.framework.lang.Sandbox; import org.opentripplanner.transit.model.framework.FeedScopedId; +/** + * A service for getting emissions information for routes. + */ @Sandbox public interface EmissionsService { + /** + * Get specific type of emissions per meter for a specific route. + * @param feedScopedRouteId + * @param emissionType + * @return Emissions per meter + */ Optional getEmissionsPerMeterForRoute( FeedScopedId feedScopedRouteId, EmissionType emissionType ); - Optional getCarEmissionsPerMeter(EmissionType emissionType); + /** + * Get specific type of emissions for a car journey. + * @param emissionType + * @return Emissions per meter + */ + Optional getEmissionsPerMeterForCar(EmissionType emissionType); } diff --git a/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java b/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java index 5101ac32244..a2863c39e75 100644 --- a/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java @@ -9,6 +9,6 @@ public static ApiEmissions mapEmissions(Emissions emissions) { if (emissions == null) { return null; } - return new ApiEmissions(emissions.getCo2Grams()); + return new ApiEmissions(emissions.getCo2()); } } diff --git a/src/main/java/org/opentripplanner/api/model/ApiEmissions.java b/src/main/java/org/opentripplanner/api/model/ApiEmissions.java index cf7e6c3cdf2..608e2eda100 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiEmissions.java +++ b/src/main/java/org/opentripplanner/api/model/ApiEmissions.java @@ -1,5 +1,7 @@ package org.opentripplanner.api.model; +import org.opentripplanner.framework.model.Grams; + /** * The emissions of an Itinerary */ @@ -7,5 +9,5 @@ public record ApiEmissions( /** * The carbon dioxide emissions of the itinerary in grams. */ - Double co2Grams + Grams co2 ) {} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index 07292cfca08..b5a6b2ef2f1 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -13,6 +13,7 @@ import org.locationtech.jts.geom.Geometry; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; +import org.opentripplanner.framework.model.Grams; public class GraphQLScalars { @@ -116,4 +117,39 @@ public Relay.ResolvedGlobalId parseLiteral(Object input) } ) .build(); + + public static GraphQLScalarType gramsScalar = GraphQLScalarType + .newScalar() + .name("Grams") + .coercing( + new Coercing() { + @Override + public Double serialize(Object dataFetcherResult) throws CoercingSerializeException { + if (dataFetcherResult instanceof Grams) { + var grams = (Grams) dataFetcherResult; + return grams.asDouble(); + } + return null; + } + + @Override + public Grams parseValue(Object input) throws CoercingParseValueException { + if (input instanceof Double) { + var grams = (Double) input; + return new Grams(grams); + } + return null; + } + + @Override + public Grams parseLiteral(Object input) throws CoercingParseLiteralException { + if (input instanceof Double) { + var grams = (Double) input; + return new Grams(grams); + } + return null; + } + } + ) + .build(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index d7b936a6198..468e5bd4d55 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -111,6 +111,7 @@ protected static GraphQLSchema buildSchema() { .scalar(GraphQLScalars.polylineScalar) .scalar(GraphQLScalars.geoJsonScalar) .scalar(GraphQLScalars.graphQLIDScalar) + .scalar(GraphQLScalars.gramsScalar) .scalar(ExtendedScalars.GraphQLLong) .type("Node", type -> type.typeResolver(new NodeTypeResolver())) .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index 2265b364b27..8e2dca49555 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -25,6 +25,7 @@ config: Long: Long Polyline: String GeoJson: org.locationtech.jts.geom.Geometry + Grams: org.opentripplanner.model.plan.Grams Duration: java.time.Duration mappers: AbsoluteDirection: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLAbsoluteDirection#GraphQLAbsoluteDirection diff --git a/src/main/java/org/opentripplanner/framework/model/Grams.java b/src/main/java/org/opentripplanner/framework/model/Grams.java new file mode 100644 index 00000000000..bf13e5b1841 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/model/Grams.java @@ -0,0 +1,28 @@ +package org.opentripplanner.framework.model; + +import static java.lang.Double.compare; + +import java.io.Serializable; + +public final class Grams implements Serializable, Comparable { + + private final Double value; + + public Grams(double value) { + this.value = value; + } + + @Override + public int compareTo(Grams o) { + return compare(value, o.value); + } + + @Override + public String toString() { + return this.value + "g"; + } + + public double asDouble() { + return this.value; + } +} diff --git a/src/main/java/org/opentripplanner/model/plan/Emissions.java b/src/main/java/org/opentripplanner/model/plan/Emissions.java index 3f2e8ed18ec..748a901898a 100644 --- a/src/main/java/org/opentripplanner/model/plan/Emissions.java +++ b/src/main/java/org/opentripplanner/model/plan/Emissions.java @@ -1,17 +1,18 @@ package org.opentripplanner.model.plan; import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.framework.model.Grams; @Sandbox public class Emissions { - private Double co2Grams; + private Grams co2; - public Double getCo2Grams() { - return co2Grams; + public Grams getCo2() { + return co2; } - public void setCo2Grams(Double co2Grams) { - this.co2Grams = co2Grams; + public void setCo2(Grams co2) { + this.co2 = co2; } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 4448cc85061..9eabdff1e15 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -904,7 +904,7 @@ type Emissions { """ CO₂ emissions in grams. """ - co2Grams: Float + co2: Grams } type fare { @@ -1008,6 +1008,8 @@ type Geometry { scalar GeoJson +scalar Grams + type StopGeometries { """Representation of the stop geometries as GeoJSON (https://geojson.org/)""" geoJson: GeoJson, diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index bd3c10f491e..89a4c36f67c 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -44,10 +44,12 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.NonLocalizedString; +import org.opentripplanner.framework.model.Grams; import org.opentripplanner.model.fare.FareMedium; import org.opentripplanner.model.fare.FareProduct; import org.opentripplanner.model.fare.ItineraryFares; import org.opentripplanner.model.fare.RiderCategory; +import org.opentripplanner.model.plan.Emissions; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.RelativeDirection; @@ -199,6 +201,10 @@ static void setup() { railLeg.addAlert(alert); + var emissions = new Emissions(); + emissions.setCo2(new Grams(123.0)); + i1.setEmissionsPerPerson(emissions); + var transitService = new DefaultTransitService(transitModel) { private final TransitAlertService alertService = new TransitAlertServiceImpl(transitModel); diff --git a/src/test/java/org/opentripplanner/generate/doc/EmissionsConfigurationDocTest.java b/src/test/java/org/opentripplanner/generate/doc/EmissionsConfigurationDocTest.java new file mode 100644 index 00000000000..68caa043e26 --- /dev/null +++ b/src/test/java/org/opentripplanner/generate/doc/EmissionsConfigurationDocTest.java @@ -0,0 +1,76 @@ +package org.opentripplanner.generate.doc; + +import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; +import static org.opentripplanner.framework.io.FileUtils.readFile; +import static org.opentripplanner.framework.io.FileUtils.writeFile; +import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; +import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; + +import java.io.File; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.application.OtpFileNames; +import org.opentripplanner.generate.doc.framework.DocBuilder; +import org.opentripplanner.generate.doc.framework.GeneratesDocumentation; +import org.opentripplanner.generate.doc.framework.ParameterDetailsList; +import org.opentripplanner.generate.doc.framework.ParameterSummaryTable; +import org.opentripplanner.generate.doc.framework.SkipNodes; +import org.opentripplanner.generate.doc.framework.TemplateUtil; +import org.opentripplanner.standalone.config.BuildConfig; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +@GeneratesDocumentation +public class EmissionsConfigurationDocTest { + + private static final File TEMPLATE = new File(TEMPLATE_ROOT, "Emissions.md"); + private static final File OUT_FILE = new File(DOCS_ROOT + "/sandbox", "Emissions.md"); + private static final String CONFIG_JSON = OtpFileNames.BUILD_CONFIG_FILENAME; + private static final String CONFIG_PATH = "standalone/config/" + CONFIG_JSON; + private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); + + @Test + public void updateEmissionsDoc() { + NodeAdapter node = readEmissionsConfig(); + + String template = readFile(TEMPLATE); + String original = readFile(OUT_FILE); + + template = replaceSection(template, "config", updaterDoc(node)); + + writeFile(OUT_FILE, template); + assertFileEquals(original, OUT_FILE); + } + + private NodeAdapter readEmissionsConfig() { + var json = jsonNodeFromResource(CONFIG_PATH); + var conf = new BuildConfig(json, CONFIG_PATH, false); + return conf.asNodeAdapter().child("emissions"); + } + + private String updaterDoc(NodeAdapter node) { + DocBuilder buf = new DocBuilder(); + addExample(buf, node); + addParameterSummaryTable(buf, node); + addDetailsSection(buf, node); + return buf.toString(); + } + + private void addParameterSummaryTable(DocBuilder buf, NodeAdapter node) { + buf + .header(3, "Overview", null) + .addSection(new ParameterSummaryTable(SKIP_NODES).createTable(node).toMarkdownTable()); + } + + private void addDetailsSection(DocBuilder buf, NodeAdapter node) { + buf + .header(3, "Details", null) + .addSection(ParameterDetailsList.listParametersWithDetails(node, SKIP_NODES, HEADER_4)); + } + + private void addExample(DocBuilder buf, NodeAdapter node) { + var root = TemplateUtil.jsonExampleBuilder(node.rawNode()).wrapInObject("emissions").build(); + buf.header(3, "Example configuration", null).addExample("build-config.json", root); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index 2bbb5a25e3b..f1dc826d053 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java @@ -18,6 +18,9 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.opentripplanner.ext.emissions.EmissionType; +import org.opentripplanner.ext.emissions.EmissionsFilter; +import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.TestItineraryBuilder; @@ -38,6 +41,7 @@ public class ItineraryListFilterChainTest implements PlanTestConstants { private Itinerary i1; private Itinerary i2; private Itinerary i3; + private Itinerary i4; @BeforeEach public void setUpItineraries() { @@ -50,6 +54,9 @@ public void setUpItineraries() { // Not optimal, departure is very late i3 = newItinerary(A).bus(20, I3_LATE_START_TIME, I3_LATE_START_TIME + D1m, E).build(); + + // car itinerary for emissions test + i4 = newItinerary(A).drive(T11_30, PlanTestConstants.T11_50, B).build(); } @Test @@ -322,4 +329,35 @@ public void removeTransitWithHigherCostThanBestOnStreetOnlyEnabled() { assertEquals(toStr(List.of(walk)), toStr(chain.filter(List.of(walk, bus)))); } } + + @Nested + class AddEmissionsToItineraryTest { + + Itinerary bus; + Itinerary car; + ItineraryListFilterChainBuilder builder = createBuilder(true, false, 2); + + @BeforeEach + public void setUpItineraries() { + bus = newItinerary(A).bus(21, T11_06, T11_09, B).build(); + car = newItinerary(A).drive(T11_30, T11_50, B).build(); + } + + @Test + public void emissionsTest() { + var emissionsService = Mockito.mock(EmissionsService.class); + + ItineraryListFilterChain chain = builder + .withEmissions(new EmissionsFilter(emissionsService)) + .build(); + chain.filter(List.of(bus, car)); + + Mockito + .verify(emissionsService, Mockito.atLeastOnce()) + .getEmissionsPerMeterForRoute(bus.getTransitLeg(0).getRoute().getId(), EmissionType.CO2); + Mockito + .verify(emissionsService, Mockito.atLeastOnce()) + .getEmissionsPerMeterForCar(EmissionType.CO2); + } + } } diff --git a/src/test/resources/gtfs/emissions-invalid-test-gtfs/emissions.txt b/src/test/resources/gtfs/emissions-invalid-test-gtfs/emissions.txt new file mode 100644 index 00000000000..ca313d4bab8 --- /dev/null +++ b/src/test/resources/gtfs/emissions-invalid-test-gtfs/emissions.txt @@ -0,0 +1,4 @@ +route_id,avg_co2_per_vehicle_per_km,avg_passenger_count +1001,1,0 +1001,-1,1 +1001,1,-1 diff --git a/src/test/resources/gtfs/emissions-invalid-test-gtfs/feed_info.txt b/src/test/resources/gtfs/emissions-invalid-test-gtfs/feed_info.txt new file mode 100644 index 00000000000..612fde11bf5 --- /dev/null +++ b/src/test/resources/gtfs/emissions-invalid-test-gtfs/feed_info.txt @@ -0,0 +1,2 @@ +feed_publisher_name,feed_publisher_url,feed_lang,feed_start_date,feed_end_date,feed_version,feed_id +emissionstest,http://www.emissionstest.fi/,fi,20230623,20230806,2023-06-23 22:42:09,emissionstest diff --git a/src/test/resources/gtfs/emissions-test-gtfs.zip b/src/test/resources/gtfs/emissions-test-gtfs.zip new file mode 100644 index 0000000000000000000000000000000000000000..146ecfd2320f288fa15aad153a99455b935e673c GIT binary patch literal 595 zcmWIWW@Zs#-~hrqkrCkxP*4S=c^MQKQgbtli!<}{iuFn=N70Q}^Z0n=QO=UiW&R zO3{1v)ON>YYx(f`o7PTUEdKD4@cTvqP7hy>N!O;B@a|g3*77dR!SX?U#F4%$8KR3G zG6#6GbNpI(oIeWaERfRz;Erw!ML7Bx)X{0FsVVW9d1?7@N1rJ*PrU%dAR5ilq1}Oe zhZF={{#SGHJ8(zx1nM~(=uB%#I-GaKYJ^Ug$0{$_q+RTNh&>>ukq+b->luI z_Sskq{ywEw@vnDd%l?w2rC+pcml!;p_CmR1V#Jy=yQgm2x{c?k&}0!q&VU6|8Sg$^ zc1AocHSK~pLpAH_cS$?^@6drEx3JXp#aiz?+o~ Date: Thu, 26 Oct 2023 09:09:52 +0300 Subject: [PATCH 32/68] Update src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java Co-authored-by: Joel Lappalainen --- .../java/org/opentripplanner/ext/emissions/EmissionsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 787b78664a7..817b38373ae 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.emissions; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; -import static shadow.org.assertj.core.api.AssertionsForClassTypes.assertThat; import java.time.OffsetDateTime; import java.time.ZonedDateTime; From 5e9bb96bb22ca8edc5549b63ad75aaf0d55e1ea5 Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 26 Oct 2023 09:10:22 +0300 Subject: [PATCH 33/68] Update src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java Co-authored-by: Joel Lappalainen --- .../java/org/opentripplanner/ext/emissions/EmissionsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 817b38373ae..5043a881e1e 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -147,7 +147,7 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { ); legs.add(leg); Itinerary i = new Itinerary(legs); - assertThat(emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2)).isEmpty(); + assertTrue(emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).isEmpty()); } @Test From d380b77b4f3fc27a628c3c6cf72d0a7166e4d762 Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 26 Oct 2023 09:50:03 +0300 Subject: [PATCH 34/68] Update src/main/java/org/opentripplanner/api/model/ApiItinerary.java Co-authored-by: Joel Lappalainen --- src/main/java/org/opentripplanner/api/model/ApiItinerary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java index 4df351e6e5e..30c04a13c05 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java +++ b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java @@ -81,7 +81,7 @@ public class ApiItinerary { public ApiItineraryFares fare = new ApiItineraryFares(Map.of(), Map.of(), null, null); /** - * The emissions of this trip. + * The emissions of this itinerary. */ public ApiEmissions emissionsPerPerson; /** From e39fb2ab4476bf2f05989ac7a8069ed52b4d13bc Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:45:53 +0300 Subject: [PATCH 35/68] Update src/main/java/org/opentripplanner/framework/model/Grams.java Co-authored-by: Leonard Ehrenfried --- src/main/java/org/opentripplanner/framework/model/Grams.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/framework/model/Grams.java b/src/main/java/org/opentripplanner/framework/model/Grams.java index bf13e5b1841..f36d6a8f69d 100644 --- a/src/main/java/org/opentripplanner/framework/model/Grams.java +++ b/src/main/java/org/opentripplanner/framework/model/Grams.java @@ -6,7 +6,7 @@ public final class Grams implements Serializable, Comparable { - private final Double value; + private final double value; public Grams(double value) { this.value = value; From 9d95379c47626da968a27f7ea9d37eb381e1d915 Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:30:17 +0300 Subject: [PATCH 36/68] Update src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java Co-authored-by: Leonard Ehrenfried --- .../java/org/opentripplanner/ext/emissions/EmissionsTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 5043a881e1e..1ce9ac529fa 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -93,7 +93,6 @@ void testGetEmissionsForItinerary() { @Test void testGetEmissionsForCarRoute() { - List legs = new ArrayList<>(); var leg = StreetLeg .create() .withMode(TraverseMode.CAR) @@ -101,8 +100,7 @@ void testGetEmissionsForCarRoute() { .withStartTime(TIME) .withEndTime(TIME.plus(1, ChronoUnit.HOURS)) .build(); - legs.add(leg); - Itinerary i = new Itinerary(legs); + Itinerary i = new Itinerary(List.of(legs)); assertEquals(28.0864, emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).get()); } From 7116132a5faa7ada357f0224b61f9184c5957ca4 Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:33:16 +0300 Subject: [PATCH 37/68] Update src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java Co-authored-by: Leonard Ehrenfried --- .../java/org/opentripplanner/ext/emissions/EmissionsTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 1ce9ac529fa..2ec9827036d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -86,8 +86,7 @@ void testGetEmissionsForItinerary() { 100, null ); - legs.add(leg); - Itinerary i = new Itinerary(legs); + Itinerary i = new Itinerary(List.of(legs)); assertEquals(2223.902, emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).get()); } From 1741481f7a8c90617f65873dc173fe69e32d4ace Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:33:46 +0300 Subject: [PATCH 38/68] Update src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java Co-authored-by: Leonard Ehrenfried --- .../java/org/opentripplanner/ext/emissions/EmissionsTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 2ec9827036d..580dbece4a6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -142,8 +142,7 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { 100, null ); - legs.add(leg); - Itinerary i = new Itinerary(legs); + Itinerary i = new Itinerary(List.of(legs)); assertTrue(emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).isEmpty()); } From d99d9032edf887cb4607c4da137e37770a5acd9f Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:33:55 +0300 Subject: [PATCH 39/68] Update src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java Co-authored-by: Leonard Ehrenfried --- .../java/org/opentripplanner/ext/emissions/EmissionsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 580dbece4a6..3dddf8097ff 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -178,7 +178,7 @@ void testZeroEmissionsForItineraryWithZeroEmissions() { null ); legs.add(leg); - Itinerary i = new Itinerary(legs); + Itinerary i = new Itinerary(List.of(legs)); assertEquals(0, emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).get()); } } From 5f333455803f101141c5a6125f4c6e0be2439e88 Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:48:24 +0300 Subject: [PATCH 40/68] Update src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java Co-authored-by: Leonard Ehrenfried --- .../org/opentripplanner/ext/emissions/EmissionsDataModel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java index dcfa7db0143..e95c0a6359e 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java @@ -8,8 +8,8 @@ public class EmissionsDataModel implements Serializable { - private Map co2Emissions; - private double carAvgCo2PerMeter; + private final Map co2Emissions; + private final double carAvgCo2PerMeter; @Inject public EmissionsDataModel() {} From ffb7adf6b1b621baba1b7652217afb847dc4d14d Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:51:09 +0300 Subject: [PATCH 41/68] Update src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java Co-authored-by: Leonard Ehrenfried --- .../org/opentripplanner/ext/emissions/EmissionsDataService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java index 74ea79834c2..8a8d8deb358 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java @@ -8,7 +8,7 @@ @Sandbox public class EmissionsDataService implements EmissionsService { - private EmissionsDataModel emissionsDataModel; + private final EmissionsDataModel emissionsDataModel; public EmissionsDataService() {} From cf1d627aaaf61724f8cb72646323a6f3d28cdecc Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 30 Oct 2023 09:35:41 +0200 Subject: [PATCH 42/68] Emissions refactoring, documentations --- docs/BuildConfiguration.md | 6 +- .../emissions/Co2EmissionsDataReaderTest.java | 32 +++--- .../ext/emissions/EmissionsTest.java | 46 ++++----- .../mapping/TripRequestMapperTest.java | 4 +- .../emissions-invalid-test-gtfs/emissions.txt | 0 .../emissions-invalid-test-gtfs/feed_info.txt | 0 .../ext/emissions}/emissions-test-gtfs.zip | Bin .../emissions-test-gtfs/emissions.txt | 0 .../emissions-test-gtfs/feed_info.txt | 0 .../ext/emissions/Co2EmissionsDataReader.java | 86 ++++++++++------ .../emissions/DefaultEmissionsService.java | 37 +++++++ .../ext/emissions/EmissionType.java | 5 - .../ext/emissions/EmissionsConfig.java | 6 +- .../ext/emissions/EmissionsDataModel.java | 11 +- .../ext/emissions/EmissionsDataService.java | 42 -------- .../ext/emissions/EmissionsFilter.java | 97 +++++++++--------- .../ext/emissions/EmissionsModule.java | 20 ++-- .../ext/emissions/EmissionsService.java | 17 ++- .../ext/emissions/EmissionsServiceModule.java | 2 +- .../api/mapping/EmissionsMapper.java | 14 --- .../api/mapping/ItineraryMapper.java | 4 - .../api/model/ApiEmissions.java | 13 --- .../api/model/ApiItinerary.java | 4 - .../apis/gtfs/GraphQLScalars.java | 2 +- .../framework/model/Grams.java | 27 +++++ .../module/configure/GraphBuilderModules.java | 5 +- .../opentripplanner/model/plan/Emissions.java | 13 ++- .../opentripplanner/TestServerContext.java | 4 +- .../apis/gtfs/GraphQLIntegrationTest.java | 3 +- .../ItineraryListFilterChainTest.java | 26 +++-- 30 files changed, 266 insertions(+), 260 deletions(-) rename src/{test/resources/gtfs => ext-test/resources/org/opentripplanner/ext/emissions}/emissions-invalid-test-gtfs/emissions.txt (100%) rename src/{test/resources/gtfs => ext-test/resources/org/opentripplanner/ext/emissions}/emissions-invalid-test-gtfs/feed_info.txt (100%) rename src/{test/resources/gtfs => ext-test/resources/org/opentripplanner/ext/emissions}/emissions-test-gtfs.zip (100%) rename src/{test/resources/gtfs => ext-test/resources/org/opentripplanner/ext/emissions}/emissions-test-gtfs/emissions.txt (100%) rename src/{test/resources/gtfs => ext-test/resources/org/opentripplanner/ext/emissions}/emissions-test-gtfs/feed_info.txt (100%) create mode 100644 src/ext/java/org/opentripplanner/ext/emissions/DefaultEmissionsService.java delete mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionType.java delete mode 100644 src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java delete mode 100644 src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java delete mode 100644 src/main/java/org/opentripplanner/api/model/ApiEmissions.java diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 3fcedbea669..6233939c2ab 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -733,9 +733,9 @@ to specify your NED tile cache location. Emissions configuration. -By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO₂) -emissions associated with transportation modes. This data is then used -to perform emission calculations for public transport modes and car travel. +By specifying the average CO₂ emissions of a car in grams per kilometer as well as +the average number of passengers in a car the program is able to to perform emission +calculations for car travel.

discardMinTransferTimes

diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java index ae9df710c91..f6385b9ec88 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java @@ -2,42 +2,38 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.HashMap; -import java.util.Map; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.graph_builder.issue.service.DefaultDataImportIssueStore; +import org.opentripplanner.test.support.ResourceLoader; public class Co2EmissionsDataReaderTest { - private static final String CO2_GTFS_ZIP_PATH = "src/test/resources/gtfs/emissions-test-gtfs.zip"; - private static final String CO2_GTFS_PATH = "src/test/resources/gtfs/emissions-test-gtfs"; - private static final String INVALID_CO2_GTFS_PATH = - "src/test/resources/gtfs/emissions-invalid-test-gtfs"; + private static final ResourceLoader RES = ResourceLoader.of(Co2EmissionsDataReaderTest.class); + private static final String CO2_GTFS_ZIP_PATH = RES.file("emissions-test-gtfs.zip").getPath(); + private static final String CO2_GTFS_PATH = RES.file("emissions-test-gtfs/").getPath(); + private static final String INVALID_CO2_GTFS_PATH = RES + .file("emissions-invalid-test-gtfs/") + .getPath(); - private Co2EmissionsDataReader co2EmissionsDataReader = new Co2EmissionsDataReader(); - private Map emissions; - - @BeforeEach - void SetUp() { - this.emissions = new HashMap<>(); - } + private Co2EmissionsDataReader co2EmissionsDataReader = new Co2EmissionsDataReader( + new DefaultDataImportIssueStore() + ); @Test void testCo2EmissionsZipDataReading() { - this.emissions = co2EmissionsDataReader.readGtfsZip(CO2_GTFS_ZIP_PATH); + var emissions = co2EmissionsDataReader.readGtfsZip(CO2_GTFS_ZIP_PATH); assertEquals(6, emissions.size()); } @Test void testCo2EmissionsDataReading() { - this.emissions = co2EmissionsDataReader.readGtfs(CO2_GTFS_PATH); + var emissions = co2EmissionsDataReader.readGtfs(CO2_GTFS_PATH); assertEquals(6, emissions.size()); } @Test void testInvalidCo2EmissionsDataReading() { - this.emissions = co2EmissionsDataReader.readGtfs(INVALID_CO2_GTFS_PATH); + var emissions = co2EmissionsDataReader.readGtfs(INVALID_CO2_GTFS_PATH); assertEquals(0, emissions.size()); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 3dddf8097ff..aff3e651c1a 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -1,7 +1,6 @@ package org.opentripplanner.ext.emissions; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import java.time.OffsetDateTime; @@ -14,9 +13,9 @@ import org.junit.jupiter.api.BeforeEach; 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.Leg; import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.StreetLeg; import org.opentripplanner.street.search.TraverseMode; @@ -30,7 +29,7 @@ class EmissionsTest { - private EmissionsDataService eService; + private DefaultEmissionsService eService; private EmissionsFilter emissionsFilter; static final ZonedDateTime TIME = OffsetDateTime @@ -48,10 +47,8 @@ void SetUp() { Map emissions = new HashMap<>(); emissions.put(new FeedScopedId("F", "1"), (0.12 / 12)); emissions.put(new FeedScopedId("F", "2"), 0.0); - EmissionsDataModel emissionsDataModel = new EmissionsDataModel(); - emissionsDataModel.setCo2Emissions(emissions); - emissionsDataModel.setCarAvgCo2PerMeter(0.131); - this.eService = new EmissionsDataService(emissionsDataModel); + EmissionsDataModel emissionsDataModel = new EmissionsDataModel(emissions, 0.131); + this.eService = new DefaultEmissionsService(emissionsDataModel); this.emissionsFilter = new EmissionsFilter(eService); } @@ -62,7 +59,6 @@ void testGetEmissionsForItinerary() { var stopThree = TransitModelForTest.stopForTest("1:stop1", 62, 25); var stopPattern = TransitModelForTest.stopPattern(stopOne, stopTwo, stopThree); var route = TransitModelForTest.route(id("1")).build(); - List legs = new ArrayList<>(); var pattern = TransitModelForTest.tripPattern("1", route).withStopPattern(stopPattern).build(); var stoptime = new StopTime(); var stoptimes = new ArrayList(); @@ -86,8 +82,11 @@ void testGetEmissionsForItinerary() { 100, null ); - Itinerary i = new Itinerary(List.of(legs)); - assertEquals(2223.902, emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).get()); + Itinerary i = new Itinerary(List.of(leg)); + assertEquals( + new Grams(2223.902), + emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2() + ); } @Test @@ -99,23 +98,23 @@ void testGetEmissionsForCarRoute() { .withStartTime(TIME) .withEndTime(TIME.plus(1, ChronoUnit.HOURS)) .build(); - Itinerary i = new Itinerary(List.of(legs)); - assertEquals(28.0864, emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).get()); + Itinerary i = new Itinerary(List.of(leg)); + assertEquals( + new Grams(28.0864), + emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2() + ); } @Test void testNoEmissionsForFeedWithoutEmissionsConfigured() { Map emissions = new HashMap<>(); emissions.put(new FeedScopedId("G", "1"), (0.12 / 12)); - EmissionsDataModel emissionsDataModel = new EmissionsDataModel(); - emissionsDataModel.setCo2Emissions(emissions); - emissionsDataModel.setCarAvgCo2PerMeter(0.131); + EmissionsDataModel emissionsDataModel = new EmissionsDataModel(emissions, 0.131); - this.eService = new EmissionsDataService(emissionsDataModel); + this.eService = new DefaultEmissionsService(emissionsDataModel); this.emissionsFilter = new EmissionsFilter(this.eService); var route = TransitModelForTest.route(id("1")).withAgency(subject).build(); - List legs = new ArrayList<>(); var pattern = TransitModelForTest .tripPattern("1", route) .withStopPattern(TransitModelForTest.stopPattern(3)) @@ -142,8 +141,8 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { 100, null ); - Itinerary i = new Itinerary(List.of(legs)); - assertTrue(emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).isEmpty()); + Itinerary i = new Itinerary(List.of(leg)); + assertEquals(null, emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2()); } @Test @@ -153,7 +152,6 @@ void testZeroEmissionsForItineraryWithZeroEmissions() { var stopThree = TransitModelForTest.stopForTest("1:stop1", 62, 25); var stopPattern = TransitModelForTest.stopPattern(stopOne, stopTwo, stopThree); var route = TransitModelForTest.route(id("2")).build(); - List legs = new ArrayList<>(); var pattern = TransitModelForTest.tripPattern("1", route).withStopPattern(stopPattern).build(); var stoptime = new StopTime(); var stoptimes = new ArrayList(); @@ -177,8 +175,10 @@ void testZeroEmissionsForItineraryWithZeroEmissions() { 100, null ); - legs.add(leg); - Itinerary i = new Itinerary(List.of(legs)); - assertEquals(0, emissionsFilter.getEmissionsForItinerary(i, EmissionType.CO2).get()); + Itinerary i = new Itinerary(List.of(leg)); + assertEquals( + new Grams(0.0), + emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2() + ); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java index 7882fb746b3..eb9058b63eb 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java @@ -24,7 +24,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.opentripplanner._support.time.ZoneIds; -import org.opentripplanner.ext.emissions.EmissionsDataService; +import org.opentripplanner.ext.emissions.DefaultEmissionsService; import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.calendar.CalendarServiceData; @@ -126,7 +126,7 @@ public class TripRequestMapperTest implements PlanTestConstants { new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()), new DefaultVehiclePositionService(), new DefaultVehicleRentalService(), - new EmissionsDataService(), + new DefaultEmissionsService(), RouterConfig.DEFAULT.flexConfig(), List.of(), null diff --git a/src/test/resources/gtfs/emissions-invalid-test-gtfs/emissions.txt b/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-invalid-test-gtfs/emissions.txt similarity index 100% rename from src/test/resources/gtfs/emissions-invalid-test-gtfs/emissions.txt rename to src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-invalid-test-gtfs/emissions.txt diff --git a/src/test/resources/gtfs/emissions-invalid-test-gtfs/feed_info.txt b/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-invalid-test-gtfs/feed_info.txt similarity index 100% rename from src/test/resources/gtfs/emissions-invalid-test-gtfs/feed_info.txt rename to src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-invalid-test-gtfs/feed_info.txt diff --git a/src/test/resources/gtfs/emissions-test-gtfs.zip b/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs.zip similarity index 100% rename from src/test/resources/gtfs/emissions-test-gtfs.zip rename to src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs.zip diff --git a/src/test/resources/gtfs/emissions-test-gtfs/emissions.txt b/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs/emissions.txt similarity index 100% rename from src/test/resources/gtfs/emissions-test-gtfs/emissions.txt rename to src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs/emissions.txt diff --git a/src/test/resources/gtfs/emissions-test-gtfs/feed_info.txt b/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs/feed_info.txt similarity index 100% rename from src/test/resources/gtfs/emissions-test-gtfs/feed_info.txt rename to src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs/feed_info.txt diff --git a/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java index a63f8c20964..9fcbd310edd 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java @@ -1,7 +1,6 @@ package org.opentripplanner.ext.emissions; import com.csvreader.CsvReader; -import com.esotericsoftware.minlog.Log; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -11,46 +10,66 @@ import java.util.Map; import java.util.Optional; import java.util.zip.ZipFile; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +/** + * This class handles reading the CO₂ emissions data from the files in the GTFS package + * and saving it in a map. + */ +@Sandbox public class Co2EmissionsDataReader { - private static final Logger LOG = LoggerFactory.getLogger(Co2EmissionsDataReader.class); - private Map emissionsData = new HashMap<>(); + private final DataImportIssueStore issueStore; - public Map readGtfs(String filePath) { - try { - InputStream feedInfoStream = new FileInputStream(filePath + "/feed_info.txt"); - String feedId = readFeedId(feedInfoStream); - feedInfoStream.close(); + public Co2EmissionsDataReader(DataImportIssueStore issueStore) { + this.issueStore = issueStore; + } - InputStream stream = new FileInputStream(filePath + "/emissions.txt"); - readEmissions(stream, feedId); - stream.close(); - return this.emissionsData; + /** + * Read files in a GTFS directory. + * @param filePath + * @return emissions data + */ + public Map readGtfs(String filePath) { + String feedId = ""; + Map emissionsData = new HashMap<>(); + try (InputStream feedInfoStream = new FileInputStream(filePath + "/feed_info.txt")) { + feedId = readFeedId(feedInfoStream); } catch (IOException e) { - LOG.error("Reading emissions data failed.", e); + issueStore.add("InvalidData", "Reading feed_info.txt failed."); } - return null; + try (InputStream stream = new FileInputStream(filePath + "/emissions.txt")) { + emissionsData = readEmissions(stream, feedId); + } catch (IOException e) { + issueStore.add("InvalidData", "Reading emissions.txt failed."); + } + return emissionsData; } + /** + * Read files in a GTFS zip file. + * @param filePath + * @return emissions data + */ public Map readGtfsZip(String filePath) { try { ZipFile zipFile = new ZipFile(new File(filePath), ZipFile.OPEN_READ); String feedId = readFeedId(zipFile.getInputStream(zipFile.getEntry("feed_info.txt"))); InputStream stream = zipFile.getInputStream(zipFile.getEntry("emissions.txt")); - readEmissions(stream, feedId); + Map emissionsData = readEmissions(stream, feedId); zipFile.close(); - return this.emissionsData; + return emissionsData; } catch (IOException e) { - LOG.error("Reading emissions data failed.", e); + issueStore.add("InvalidData", "Reading emissions data failed."); } return null; } - private void readEmissions(InputStream stream, String feedId) throws IOException { + private Map readEmissions(InputStream stream, String feedId) + throws IOException { + Map emissionsData = new HashMap<>(); CsvReader reader = new CsvReader(stream, StandardCharsets.UTF_8); reader.readHeaders(); @@ -60,10 +79,18 @@ private void readEmissions(InputStream stream, String feedId) throws IOException String avgPassengerCountString = reader.get("avg_passenger_count"); if (avgCo2PerVehiclePerKmString.isEmpty()) { - LOG.error("Value for avg_co2_per_vehicle_per_km is missing in the Emissions.txt"); + issueStore.add( + "InvalidData", + "Value for avg_co2_per_vehicle_per_km is missing in the Emissions.txt for route %s", + routeId + ); } if (avgPassengerCountString.isEmpty()) { - LOG.error("Value for avg_passenger_count is missing in the Emissions.txt"); + issueStore.add( + "InvalidData", + "Value for avg_passenger_count is missing in the Emissions.txt for route %s", + routeId + ); } Double avgCo2PerVehiclePerMeter = Double.parseDouble(avgCo2PerVehiclePerKmString) / 1000; @@ -74,9 +101,10 @@ private void readEmissions(InputStream stream, String feedId) throws IOException avgPassengerCount ); if (emissions.isPresent()) { - this.emissionsData.put(new FeedScopedId(feedId, routeId), emissions.get()); + emissionsData.put(new FeedScopedId(feedId, routeId), emissions.get()); } } + return emissionsData; } private String readFeedId(InputStream stream) { @@ -86,12 +114,12 @@ private String readFeedId(InputStream stream) { reader.readRecord(); return reader.get("feed_id"); } catch (IOException e) { - LOG.error("Reading feed id for emissions failed.", e); + issueStore.add("InvalidData", "Reading emissions data failed."); throw new RuntimeException(e); } } - private static Optional calculateEmissionsPerPassengerPerMeter( + private Optional calculateEmissionsPerPassengerPerMeter( String routeId, double avgCo2PerVehiclePerMeter, double avgPassengerCount @@ -101,10 +129,10 @@ private static Optional calculateEmissionsPerPassengerPerMeter( return Optional.of(avgCo2PerVehiclePerMeter); } if (avgPassengerCount <= 0 || avgCo2PerVehiclePerMeter < 0) { - Log.error( - "Invalid data for route " + - routeId + - ": avgPassengerCount is 0 or less, but avgCo2PerVehiclePerMeter is nonzero or avgCo2PerVehiclePerMeter is negative." + issueStore.add( + "InvalidData", + "avgPassengerCount is 0 or less, but avgCo2PerVehiclePerMeter is nonzero or avgCo2PerVehiclePerMeter is negative for route %s", + routeId ); return Optional.empty(); } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DefaultEmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/DefaultEmissionsService.java new file mode 100644 index 00000000000..589a849a4d7 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/emissions/DefaultEmissionsService.java @@ -0,0 +1,37 @@ +package org.opentripplanner.ext.emissions; + +import jakarta.inject.Inject; +import java.util.Optional; +import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.framework.model.Grams; +import org.opentripplanner.model.plan.Emissions; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +@Sandbox +public class DefaultEmissionsService implements EmissionsService { + + private EmissionsDataModel emissionsDataModel; + + public DefaultEmissionsService() {} + + @Inject + public DefaultEmissionsService(EmissionsDataModel emissionsDataModel) { + this.emissionsDataModel = emissionsDataModel; + } + + @Override + public Optional getEmissionsPerMeterForRoute(FeedScopedId feedScopedRouteId) { + Optional co2Emissions = this.emissionsDataModel.getCO2EmissionsById(feedScopedRouteId); + return co2Emissions.isPresent() + ? Optional.of(new Emissions(new Grams(co2Emissions.get()))) + : Optional.empty(); + } + + @Override + public Optional getEmissionsPerMeterForCar() { + Optional co2Emissions = this.emissionsDataModel.getCarAvgCo2PerMeter(); + return co2Emissions.isPresent() + ? Optional.of(new Emissions(new Grams(co2Emissions.get()))) + : Optional.empty(); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionType.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionType.java deleted file mode 100644 index df904a333bf..00000000000 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionType.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.opentripplanner.ext.emissions; - -public enum EmissionType { - CO2, -} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsConfig.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsConfig.java index 9f4a29610b1..ed95efa0d4c 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsConfig.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsConfig.java @@ -16,9 +16,9 @@ public EmissionsConfig(String parameterName, NodeAdapter root) { .summary("Emissions configuration.") .description( """ - By specifying a URL to fetch emissions data, the program gains access to carbon dioxide (CO₂) - emissions associated with transportation modes. This data is then used - to perform emission calculations for public transport modes and car travel. + By specifying the average CO₂ emissions of a car in grams per kilometer as well as + the average number of passengers in a car the program is able to to perform emission + calculations for car travel. """ ) .asObject(); diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java index e95c0a6359e..fa4180380bd 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataModel.java @@ -6,10 +6,13 @@ import java.util.Optional; import org.opentripplanner.transit.model.framework.FeedScopedId; +/** + * Container for emissions data. + */ public class EmissionsDataModel implements Serializable { - private final Map co2Emissions; - private final double carAvgCo2PerMeter; + private Map co2Emissions; + private Double carAvgCo2PerMeter; @Inject public EmissionsDataModel() {} @@ -27,8 +30,8 @@ public void setCarAvgCo2PerMeter(double carAvgCo2PerMeter) { this.carAvgCo2PerMeter = carAvgCo2PerMeter; } - public Double getCarAvgCo2PerMeter() { - return this.carAvgCo2PerMeter; + public Optional getCarAvgCo2PerMeter() { + return Optional.ofNullable(this.carAvgCo2PerMeter); } public Optional getCO2EmissionsById(FeedScopedId feedScopedRouteId) { diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java deleted file mode 100644 index 8a8d8deb358..00000000000 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsDataService.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.opentripplanner.ext.emissions; - -import jakarta.inject.Inject; -import java.util.Optional; -import org.opentripplanner.framework.lang.Sandbox; -import org.opentripplanner.transit.model.framework.FeedScopedId; - -@Sandbox -public class EmissionsDataService implements EmissionsService { - - private final EmissionsDataModel emissionsDataModel; - - public EmissionsDataService() {} - - @Inject - public EmissionsDataService(EmissionsDataModel emissionsDataModel) { - this.emissionsDataModel = emissionsDataModel; - } - - @Override - public Optional getEmissionsPerMeterForRoute( - FeedScopedId feedScopedRouteId, - EmissionType emissionType - ) { - switch (emissionType) { - case CO2: - return this.emissionsDataModel.getCO2EmissionsById(feedScopedRouteId); - default: - return Optional.empty(); - } - } - - @Override - public Optional getEmissionsPerMeterForCar(EmissionType emissionType) { - switch (emissionType) { - case CO2: - return Optional.ofNullable(this.emissionsDataModel.getCarAvgCo2PerMeter()); - default: - return Optional.empty(); - } - } -} diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java index 94cdb397701..c3ea01bd751 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java @@ -14,81 +14,78 @@ import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.transit.model.framework.FeedScopedId; +/** + * Calculates the emissions for the itineraries and adds them. + * @param emissionsService + */ @Sandbox public record EmissionsFilter(EmissionsService emissionsService) implements ItineraryListFilter { @Override public List filter(List itineraries) { - for (Itinerary i : itineraries) { - Emissions emissions = new Emissions(); + for (Itinerary itinerary : itineraries) { + List transitLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg) + .map(TransitLeg.class::cast) + .toList(); - Optional carbonDioxide = this.getEmissionsForItinerary(i, EmissionType.CO2); - if (carbonDioxide.isPresent()) { - emissions.setCo2(new Grams(carbonDioxide.get())); + if (!transitLegs.isEmpty()) { + Optional co2Emissions = calculateCo2EmissionsForTransit(transitLegs); + Grams co2 = co2Emissions.isPresent() ? co2Emissions.get() : null; + itinerary.setEmissionsPerPerson(new Emissions(co2)); } - i.setEmissionsPerPerson(emissions); - } - return itineraries; - } - - public Optional getEmissionsForItinerary(Itinerary itinerary, EmissionType emissionType) { - List transitLegs = itinerary - .getLegs() - .stream() - .filter(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg) - .map(TransitLeg.class::cast) - .toList(); - - if (!transitLegs.isEmpty()) { - return Optional.ofNullable(calculateEmissionsForTransit(transitLegs, emissionType)); - } - - List carLegs = itinerary - .getLegs() - .stream() - .filter(l -> l instanceof StreetLeg) - .map(StreetLeg.class::cast) - .filter(leg -> leg.getMode() == TraverseMode.CAR) - .toList(); + List carLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof StreetLeg) + .map(StreetLeg.class::cast) + .filter(leg -> leg.getMode() == TraverseMode.CAR) + .toList(); - if (!carLegs.isEmpty()) { - return calculateEmissionsForCar(carLegs, emissionType); + if (!carLegs.isEmpty()) { + Optional carCo2Emissions = calculateCo2EmissionsForCar(carLegs); + Grams co2 = carCo2Emissions.isPresent() ? carCo2Emissions.get() : null; + itinerary.setEmissionsPerPerson(new Emissions(co2)); + } } - return Optional.empty(); + return itineraries; } - private Double calculateEmissionsForTransit( - List transitLegs, - EmissionType emissionType - ) { - Double emissions = 0.0; + private Optional calculateCo2EmissionsForTransit(List transitLegs) { + Grams co2Emissions = new Grams(0.0); for (TransitLeg leg : transitLegs) { FeedScopedId feedScopedRouteId = new FeedScopedId( leg.getAgency().getId().getFeedId(), leg.getRoute().getId().getId() ); - Optional emissionsForRoute = emissionsService.getEmissionsPerMeterForRoute( - feedScopedRouteId, - emissionType + Optional co2EmissionsForRoute = emissionsService.getEmissionsPerMeterForRoute( + feedScopedRouteId ); - if (emissionsForRoute.isPresent()) { - emissions += emissionsForRoute.get() * leg.getDistanceMeters(); + if (co2EmissionsForRoute.isPresent()) { + co2Emissions = + co2Emissions.plus(co2EmissionsForRoute.get().getCo2().multiply(leg.getDistanceMeters())); } else { // Partial results would not give an accurate representation of the emissions. - return null; + return Optional.empty(); } } - return emissions; + return Optional.of(co2Emissions); } - private Optional calculateEmissionsForCar( - List carLegs, - EmissionType emissionType - ) { - Optional emissionsForCar = emissionsService.getEmissionsPerMeterForCar(emissionType); + private Optional calculateCo2EmissionsForCar(List carLegs) { + Optional emissionsForCar = emissionsService.getEmissionsPerMeterForCar(); if (emissionsForCar.isPresent()) { return Optional.of( - carLegs.stream().mapToDouble(leg -> emissionsForCar.get() * leg.getDistanceMeters()).sum() + new Grams( + carLegs + .stream() + .mapToDouble(leg -> + emissionsForCar.get().getCo2().multiply(leg.getDistanceMeters()).asDouble() + ) + .sum() + ) ); } return Optional.empty(); diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java index e46fedd7431..c0b4141b041 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java @@ -1,18 +1,12 @@ package org.opentripplanner.ext.emissions; -import com.csvreader.CsvReader; import dagger.Module; import jakarta.inject.Inject; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -import java.util.zip.ZipFile; import org.opentripplanner.graph_builder.ConfiguredDataSource; import org.opentripplanner.graph_builder.GraphBuilderDataSources; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.model.GraphBuilderModule; import org.opentripplanner.gtfs.graphbuilder.GtfsFeedParameters; import org.opentripplanner.standalone.config.BuildConfig; @@ -20,6 +14,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This class allows updating the graph with emissions data from external emissions data files. + */ @Module public class EmissionsModule implements GraphBuilderModule { @@ -28,22 +25,25 @@ public class EmissionsModule implements GraphBuilderModule { private EmissionsDataModel emissionsDataModel; private GraphBuilderDataSources dataSources; private Map emissionsData = new HashMap<>(); + private final DataImportIssueStore issueStore; @Inject public EmissionsModule( GraphBuilderDataSources dataSources, BuildConfig config, - EmissionsDataModel emissionsDataModel + EmissionsDataModel emissionsDataModel, + DataImportIssueStore issueStore ) { this.dataSources = dataSources; this.config = config; this.emissionsDataModel = emissionsDataModel; + this.issueStore = issueStore; } public void buildGraph() { if (config.emissions != null) { - LOG.info("Start emissions building!"); - Co2EmissionsDataReader co2EmissionsDataReader = new Co2EmissionsDataReader(); + LOG.info("Start emissions building"); + Co2EmissionsDataReader co2EmissionsDataReader = new Co2EmissionsDataReader(issueStore); double carAvgCo2PerKm = config.emissions.getCarAvgCo2PerKm(); double carAvgOccupancy = config.emissions.getCarAvgOccupancy(); double carAvgEmissionsPerMeter = carAvgCo2PerKm / 1000 / carAvgOccupancy; diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java index a6e9372a415..6f69ac60d06 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsService.java @@ -2,6 +2,7 @@ import java.util.Optional; import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.model.plan.Emissions; import org.opentripplanner.transit.model.framework.FeedScopedId; /** @@ -10,20 +11,16 @@ @Sandbox public interface EmissionsService { /** - * Get specific type of emissions per meter for a specific route. - * @param feedScopedRouteId - * @param emissionType + * Get all emissions per meter for a specific route. + * * @return Emissions per meter */ - Optional getEmissionsPerMeterForRoute( - FeedScopedId feedScopedRouteId, - EmissionType emissionType - ); + Optional getEmissionsPerMeterForRoute(FeedScopedId feedScopedRouteId); /** - * Get specific type of emissions for a car journey. - * @param emissionType + * Get all emissions per meter for a car. + * * @return Emissions per meter */ - Optional getEmissionsPerMeterForCar(EmissionType emissionType); + Optional getEmissionsPerMeterForCar(); } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsServiceModule.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsServiceModule.java index 256a3024c4a..47ee043e1b5 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsServiceModule.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsServiceModule.java @@ -14,6 +14,6 @@ public class EmissionsServiceModule { @Provides @Singleton public EmissionsService provideEmissionsService(EmissionsDataModel emissionsDataModel) { - return new EmissionsDataService(emissionsDataModel); + return new DefaultEmissionsService(emissionsDataModel); } } diff --git a/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java b/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java deleted file mode 100644 index a2863c39e75..00000000000 --- a/src/main/java/org/opentripplanner/api/mapping/EmissionsMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.opentripplanner.api.mapping; - -import org.opentripplanner.api.model.ApiEmissions; -import org.opentripplanner.model.plan.Emissions; - -public class EmissionsMapper { - - public static ApiEmissions mapEmissions(Emissions emissions) { - if (emissions == null) { - return null; - } - return new ApiEmissions(emissions.getCo2()); - } -} diff --git a/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java b/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java index 08036a5cb5d..cf14eda73dd 100644 --- a/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/ItineraryMapper.java @@ -12,12 +12,10 @@ public class ItineraryMapper { private final LegMapper legMapper; private final FareMapper fareMapper; - private final EmissionsMapper emissionsMapper; public ItineraryMapper(Locale locale, boolean addIntermediateStops) { this.legMapper = new LegMapper(locale, addIntermediateStops); this.fareMapper = new FareMapper(locale); - this.emissionsMapper = new EmissionsMapper(); } public List mapItineraries(Collection domain) { @@ -32,7 +30,6 @@ public ApiItinerary mapItinerary(Itinerary domain) { return null; } ApiItinerary api = new ApiItinerary(); - api.duration = domain.getDuration().toSeconds(); api.startTime = GregorianCalendar.from(domain.startTime()); api.endTime = GregorianCalendar.from(domain.endTime()); @@ -47,7 +44,6 @@ public ApiItinerary mapItinerary(Itinerary domain) { api.tooSloped = domain.isTooSloped(); api.arrivedAtDestinationWithRentedBicycle = domain.isArrivedAtDestinationWithRentedVehicle(); api.fare = fareMapper.mapFare(domain); - api.emissionsPerPerson = emissionsMapper.mapEmissions(domain.getEmissionsPerPerson()); api.legs = legMapper.mapLegs(domain.getLegs()); api.systemNotices = SystemNoticeMapper.mapSystemNotices(domain.getSystemNotices()); api.accessibilityScore = domain.getAccessibilityScore(); diff --git a/src/main/java/org/opentripplanner/api/model/ApiEmissions.java b/src/main/java/org/opentripplanner/api/model/ApiEmissions.java deleted file mode 100644 index 608e2eda100..00000000000 --- a/src/main/java/org/opentripplanner/api/model/ApiEmissions.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.opentripplanner.api.model; - -import org.opentripplanner.framework.model.Grams; - -/** - * The emissions of an Itinerary - */ -public record ApiEmissions( - /** - * The carbon dioxide emissions of the itinerary in grams. - */ - Grams co2 -) {} diff --git a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java index 30c04a13c05..e2d205de13e 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiItinerary.java +++ b/src/main/java/org/opentripplanner/api/model/ApiItinerary.java @@ -80,10 +80,6 @@ public class ApiItinerary { */ public ApiItineraryFares fare = new ApiItineraryFares(Map.of(), Map.of(), null, null); - /** - * The emissions of this itinerary. - */ - public ApiEmissions emissionsPerPerson; /** * A list of Legs. Each Leg is either a walking (cycling, car) portion of the trip, or a transit * trip on a particular vehicle. So a trip where the use walks to the Q train, transfers to the 6, diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index b5a6b2ef2f1..6280ac28ac2 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -127,7 +127,7 @@ public Relay.ResolvedGlobalId parseLiteral(Object input) public Double serialize(Object dataFetcherResult) throws CoercingSerializeException { if (dataFetcherResult instanceof Grams) { var grams = (Grams) dataFetcherResult; - return grams.asDouble(); + return Double.valueOf(grams.asDouble()); } return null; } diff --git a/src/main/java/org/opentripplanner/framework/model/Grams.java b/src/main/java/org/opentripplanner/framework/model/Grams.java index f36d6a8f69d..86e8adce5b1 100644 --- a/src/main/java/org/opentripplanner/framework/model/Grams.java +++ b/src/main/java/org/opentripplanner/framework/model/Grams.java @@ -4,6 +4,9 @@ import java.io.Serializable; +/** + * A representation of the weight of something in grams. + */ public final class Grams implements Serializable, Comparable { private final double value; @@ -12,6 +15,30 @@ public Grams(double value) { this.value = value; } + public Grams plus(Grams g) { + return new Grams(this.value + g.value); + } + + public Grams multiply(int factor) { + return new Grams(this.value * factor); + } + + public Grams multiply(double factor) { + return new Grams(this.value * factor); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (Grams) o; + return value == that.value; + } + @Override public int compareTo(Grams o) { return compare(value, o.value); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 931d9885f32..faddf4af3b2 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -113,9 +113,10 @@ static GtfsModule provideGtfsModule( static EmissionsModule provideEmissionsModule( GraphBuilderDataSources dataSources, BuildConfig config, - EmissionsDataModel emissionsDataModel + EmissionsDataModel emissionsDataModel, + DataImportIssueStore issueStore ) { - return new EmissionsModule(dataSources, config, emissionsDataModel); + return new EmissionsModule(dataSources, config, emissionsDataModel, issueStore); } @Provides diff --git a/src/main/java/org/opentripplanner/model/plan/Emissions.java b/src/main/java/org/opentripplanner/model/plan/Emissions.java index 748a901898a..e19ce3a914a 100644 --- a/src/main/java/org/opentripplanner/model/plan/Emissions.java +++ b/src/main/java/org/opentripplanner/model/plan/Emissions.java @@ -3,16 +3,21 @@ import org.opentripplanner.framework.lang.Sandbox; import org.opentripplanner.framework.model.Grams; +/** + * Represents the emissions of a journey. Each type of emissions has its own field and unit. + */ @Sandbox public class Emissions { private Grams co2; - public Grams getCo2() { - return co2; + public Emissions(Grams co2) { + if (co2 != null) { + this.co2 = co2; + } } - public void setCo2(Grams co2) { - this.co2 = co2; + public Grams getCo2() { + return co2; } } diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index 43573dd2621..e17a2e65c58 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -4,7 +4,7 @@ import io.micrometer.core.instrument.Metrics; import java.util.List; -import org.opentripplanner.ext.emissions.EmissionsDataService; +import org.opentripplanner.ext.emissions.DefaultEmissionsService; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.graph.Graph; @@ -66,6 +66,6 @@ public static VehicleRentalService createVehicleRentalService() { } public static EmissionsService createEmissionsService() { - return new EmissionsDataService(); + return new DefaultEmissionsService(); } } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 89a4c36f67c..e1aa8d05cb5 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -201,8 +201,7 @@ static void setup() { railLeg.addAlert(alert); - var emissions = new Emissions(); - emissions.setCo2(new Grams(123.0)); + var emissions = new Emissions(new Grams(123.0)); i1.setEmissionsPerPerson(emissions); var transitService = new DefaultTransitService(transitModel) { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index f1dc826d053..a5c8ba92be3 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java @@ -13,12 +13,15 @@ import java.time.Duration; import java.time.Instant; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.opentripplanner.ext.emissions.EmissionType; +import org.opentripplanner.ext.emissions.DefaultEmissionsService; +import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.ext.emissions.EmissionsFilter; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.model.plan.Itinerary; @@ -29,6 +32,7 @@ import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.services.TransitAlertService; +import org.opentripplanner.transit.model.framework.FeedScopedId; /** * This class test the whole filter chain with a few test cases. Each filter should be tested with a @@ -336,28 +340,22 @@ class AddEmissionsToItineraryTest { Itinerary bus; Itinerary car; ItineraryListFilterChainBuilder builder = createBuilder(true, false, 2); + EmissionsService eService; @BeforeEach public void setUpItineraries() { bus = newItinerary(A).bus(21, T11_06, T11_09, B).build(); car = newItinerary(A).drive(T11_30, T11_50, B).build(); + Map emissions = new HashMap<>(); + emissions.put(new FeedScopedId("F", "1"), 1.0); + eService = new DefaultEmissionsService(new EmissionsDataModel(emissions, 1.0)); } @Test public void emissionsTest() { - var emissionsService = Mockito.mock(EmissionsService.class); - - ItineraryListFilterChain chain = builder - .withEmissions(new EmissionsFilter(emissionsService)) - .build(); - chain.filter(List.of(bus, car)); - - Mockito - .verify(emissionsService, Mockito.atLeastOnce()) - .getEmissionsPerMeterForRoute(bus.getTransitLeg(0).getRoute().getId(), EmissionType.CO2); - Mockito - .verify(emissionsService, Mockito.atLeastOnce()) - .getEmissionsPerMeterForCar(EmissionType.CO2); + ItineraryListFilterChain chain = builder.withEmissions(new EmissionsFilter(eService)).build(); + List itineraries = chain.filter(List.of(bus, car)); + assertFalse(itineraries.stream().anyMatch(i -> i.getEmissionsPerPerson().getCo2() == null)); } } } From bab72fc626880a8cb800682b04c102d94ab5a1fe Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 30 Oct 2023 11:04:55 +0200 Subject: [PATCH 43/68] emissions service final field --- .../ext/transmodelapi/mapping/TripRequestMapperTest.java | 3 ++- .../ext/emissions/DefaultEmissionsService.java | 4 +--- src/test/java/org/opentripplanner/TestServerContext.java | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java index eb9058b63eb..7817d7c6e4d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java @@ -25,6 +25,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.ext.emissions.DefaultEmissionsService; +import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.calendar.CalendarServiceData; @@ -126,7 +127,7 @@ public class TripRequestMapperTest implements PlanTestConstants { new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()), new DefaultVehiclePositionService(), new DefaultVehicleRentalService(), - new DefaultEmissionsService(), + new DefaultEmissionsService(new EmissionsDataModel()), RouterConfig.DEFAULT.flexConfig(), List.of(), null diff --git a/src/ext/java/org/opentripplanner/ext/emissions/DefaultEmissionsService.java b/src/ext/java/org/opentripplanner/ext/emissions/DefaultEmissionsService.java index 589a849a4d7..5df2ca17f26 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/DefaultEmissionsService.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/DefaultEmissionsService.java @@ -10,9 +10,7 @@ @Sandbox public class DefaultEmissionsService implements EmissionsService { - private EmissionsDataModel emissionsDataModel; - - public DefaultEmissionsService() {} + private final EmissionsDataModel emissionsDataModel; @Inject public DefaultEmissionsService(EmissionsDataModel emissionsDataModel) { diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index e17a2e65c58..06604da4498 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -5,6 +5,7 @@ import io.micrometer.core.instrument.Metrics; import java.util.List; import org.opentripplanner.ext.emissions.DefaultEmissionsService; +import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.graph.Graph; @@ -66,6 +67,6 @@ public static VehicleRentalService createVehicleRentalService() { } public static EmissionsService createEmissionsService() { - return new DefaultEmissionsService(); + return new DefaultEmissionsService(new EmissionsDataModel()); } } From 471e0ebc6ee4e3cfc8a4503fddc8e472084cbee0 Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:19:31 +0200 Subject: [PATCH 44/68] Update src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java Co-authored-by: Leonard Ehrenfried --- .../ext/emissions/EmissionsFilter.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java index c3ea01bd751..97cc52fcc49 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java @@ -75,19 +75,15 @@ private Optional calculateCo2EmissionsForTransit(List transit } private Optional calculateCo2EmissionsForCar(List carLegs) { - Optional emissionsForCar = emissionsService.getEmissionsPerMeterForCar(); - if (emissionsForCar.isPresent()) { - return Optional.of( - new Grams( + return emissionsService.getEmissionsPerMeterForCar().map( emissions -> { + return new Grams( carLegs .stream() .mapToDouble(leg -> emissionsForCar.get().getCo2().multiply(leg.getDistanceMeters()).asDouble() ) .sum() - ) - ); - } - return Optional.empty(); + ); + }); } } From 7dc9b2a6cbd121b175694f230d2707f458df59c0 Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 30 Oct 2023 13:28:53 +0200 Subject: [PATCH 45/68] emissions refactoring --- .../ext/emissions/EmissionsFilter.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java index 97cc52fcc49..a6af1a05d43 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java @@ -30,11 +30,10 @@ public List filter(List itineraries) { .map(TransitLeg.class::cast) .toList(); - if (!transitLegs.isEmpty()) { - Optional co2Emissions = calculateCo2EmissionsForTransit(transitLegs); - Grams co2 = co2Emissions.isPresent() ? co2Emissions.get() : null; - itinerary.setEmissionsPerPerson(new Emissions(co2)); - } + calculateCo2EmissionsForTransit(transitLegs) + .ifPresent(co2 -> { + itinerary.setEmissionsPerPerson(new Emissions(co2)); + }); List carLegs = itinerary .getLegs() @@ -44,11 +43,10 @@ public List filter(List itineraries) { .filter(leg -> leg.getMode() == TraverseMode.CAR) .toList(); - if (!carLegs.isEmpty()) { - Optional carCo2Emissions = calculateCo2EmissionsForCar(carLegs); - Grams co2 = carCo2Emissions.isPresent() ? carCo2Emissions.get() : null; - itinerary.setEmissionsPerPerson(new Emissions(co2)); - } + calculateCo2EmissionsForCar(carLegs) + .ifPresent(co2 -> { + itinerary.setEmissionsPerPerson(new Emissions(co2)); + }); } return itineraries; } @@ -75,15 +73,15 @@ private Optional calculateCo2EmissionsForTransit(List transit } private Optional calculateCo2EmissionsForCar(List carLegs) { - return emissionsService.getEmissionsPerMeterForCar().map( emissions -> { - return new Grams( + return emissionsService + .getEmissionsPerMeterForCar() + .map(emissions -> + new Grams( carLegs .stream() - .mapToDouble(leg -> - emissionsForCar.get().getCo2().multiply(leg.getDistanceMeters()).asDouble() - ) + .mapToDouble(leg -> emissions.getCo2().multiply(leg.getDistanceMeters()).asDouble()) .sum() - ); - }); + ) + ); } } From 0b7e4142d2a74725a550db553e60de9b8cb22aa4 Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 30 Oct 2023 14:28:40 +0200 Subject: [PATCH 46/68] Emissions refactoring and test fixes --- docs/apis/GraphQL-Tutorial.md | 3 --- .../org/opentripplanner/ext/emissions/EmissionsTest.java | 2 +- .../opentripplanner/ext/emissions/EmissionsFilter.java | 8 +++++++- .../apis/gtfs/expectations/plan-extended.json | 3 +++ .../org/opentripplanner/apis/gtfs/expectations/plan.json | 3 --- .../apis/gtfs/queries/plan-extended.graphql | 3 +++ .../org/opentripplanner/apis/gtfs/queries/plan.graphql | 3 --- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/apis/GraphQL-Tutorial.md b/docs/apis/GraphQL-Tutorial.md index 1f849f392c7..8df6d6f38ee 100644 --- a/docs/apis/GraphQL-Tutorial.md +++ b/docs/apis/GraphQL-Tutorial.md @@ -90,9 +90,6 @@ Most people want to get routing results out of OTP, so lets see the query for th itineraries { startTime endTime - emissionsPerPerson { - co2 - } legs { mode startTime diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 11e07ba4a26..ee8f148faa6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -135,7 +135,7 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { .withZoneId(ZoneIds.BERLIN) .build(); Itinerary i = new Itinerary(List.of(leg)); - assertEquals(null, emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2()); + assertEquals(null, emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson()); } @Test diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java index a6af1a05d43..845bb42204d 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java @@ -52,6 +52,9 @@ public List filter(List itineraries) { } private Optional calculateCo2EmissionsForTransit(List transitLegs) { + if (transitLegs.isEmpty()) { + return Optional.empty(); + } Grams co2Emissions = new Grams(0.0); for (TransitLeg leg : transitLegs) { FeedScopedId feedScopedRouteId = new FeedScopedId( @@ -69,10 +72,13 @@ private Optional calculateCo2EmissionsForTransit(List transit return Optional.empty(); } } - return Optional.of(co2Emissions); + return Optional.ofNullable(co2Emissions); } private Optional calculateCo2EmissionsForCar(List carLegs) { + if (carLegs.isEmpty()) { + return Optional.empty(); + } return emissionsService .getEmissionsPerMeterForCar() .map(emissions -> diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json index 95c96f3c8d7..2ab0b978cf0 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json @@ -7,6 +7,9 @@ "endTime" : 1580644800000, "generalizedCost" : 4072, "accessibilityScore" : 0.5, + "emissionsPerPerson" : { + "co2" : 123.0 + }, "legs" : [ { "mode" : "WALK", diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan.json index 9f625fc1a61..06fd20be150 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan.json @@ -5,9 +5,6 @@ { "startTime" : 1580641200000, "endTime" : 1580644800000, - "emissionsPerPerson" : { - "co2": 123.0 - }, "legs" : [ { "mode" : "WALK", diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index 0ffc31b50b9..7fdc24822a4 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -20,6 +20,9 @@ endTime generalizedCost accessibilityScore + emissionsPerPerson { + co2 + } legs { mode from { diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql index ac239d885ab..d71c991234d 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql @@ -19,9 +19,6 @@ itineraries { startTime endTime - emissionsPerPerson { - co2 - } legs { mode startTime From f12803eb0c158c549070e2d8a2d477204d7ba14c Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 31 Oct 2023 09:02:49 +0200 Subject: [PATCH 47/68] Emissions file fix --- .../ext/emissions/Co2EmissionsDataReaderTest.java | 15 +++++++-------- .../ext/emissions/Co2EmissionsDataReader.java | 14 +++++++------- .../ext/emissions/EmissionsModule.java | 5 +++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java index f6385b9ec88..235badd9f48 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReaderTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.File; import org.junit.jupiter.api.Test; import org.opentripplanner.graph_builder.issue.service.DefaultDataImportIssueStore; import org.opentripplanner.test.support.ResourceLoader; @@ -9,11 +10,9 @@ public class Co2EmissionsDataReaderTest { private static final ResourceLoader RES = ResourceLoader.of(Co2EmissionsDataReaderTest.class); - private static final String CO2_GTFS_ZIP_PATH = RES.file("emissions-test-gtfs.zip").getPath(); - private static final String CO2_GTFS_PATH = RES.file("emissions-test-gtfs/").getPath(); - private static final String INVALID_CO2_GTFS_PATH = RES - .file("emissions-invalid-test-gtfs/") - .getPath(); + private static final File CO2_GTFS_ZIP = RES.file("emissions-test-gtfs.zip"); + private static final File CO2_GTFS = RES.file("emissions-test-gtfs/"); + private static final File INVALID_CO2_GTFS = RES.file("emissions-invalid-test-gtfs/"); private Co2EmissionsDataReader co2EmissionsDataReader = new Co2EmissionsDataReader( new DefaultDataImportIssueStore() @@ -21,19 +20,19 @@ public class Co2EmissionsDataReaderTest { @Test void testCo2EmissionsZipDataReading() { - var emissions = co2EmissionsDataReader.readGtfsZip(CO2_GTFS_ZIP_PATH); + var emissions = co2EmissionsDataReader.readGtfsZip(CO2_GTFS_ZIP); assertEquals(6, emissions.size()); } @Test void testCo2EmissionsDataReading() { - var emissions = co2EmissionsDataReader.readGtfs(CO2_GTFS_PATH); + var emissions = co2EmissionsDataReader.readGtfs(CO2_GTFS); assertEquals(6, emissions.size()); } @Test void testInvalidCo2EmissionsDataReading() { - var emissions = co2EmissionsDataReader.readGtfs(INVALID_CO2_GTFS_PATH); + var emissions = co2EmissionsDataReader.readGtfs(INVALID_CO2_GTFS); assertEquals(0, emissions.size()); } } diff --git a/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java index 9fcbd310edd..1e822cb3d10 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java @@ -29,18 +29,18 @@ public Co2EmissionsDataReader(DataImportIssueStore issueStore) { /** * Read files in a GTFS directory. - * @param filePath + * @param directory * @return emissions data */ - public Map readGtfs(String filePath) { + public Map readGtfs(File directory) { String feedId = ""; Map emissionsData = new HashMap<>(); - try (InputStream feedInfoStream = new FileInputStream(filePath + "/feed_info.txt")) { + try (InputStream feedInfoStream = new FileInputStream(directory + "/feed_info.txt")) { feedId = readFeedId(feedInfoStream); } catch (IOException e) { issueStore.add("InvalidData", "Reading feed_info.txt failed."); } - try (InputStream stream = new FileInputStream(filePath + "/emissions.txt")) { + try (InputStream stream = new FileInputStream(directory + "/emissions.txt")) { emissionsData = readEmissions(stream, feedId); } catch (IOException e) { issueStore.add("InvalidData", "Reading emissions.txt failed."); @@ -50,12 +50,12 @@ public Map readGtfs(String filePath) { /** * Read files in a GTFS zip file. - * @param filePath + * @param file * @return emissions data */ - public Map readGtfsZip(String filePath) { + public Map readGtfsZip(File file) { try { - ZipFile zipFile = new ZipFile(new File(filePath), ZipFile.OPEN_READ); + ZipFile zipFile = new ZipFile(file, ZipFile.OPEN_READ); String feedId = readFeedId(zipFile.getInputStream(zipFile.getEntry("feed_info.txt"))); InputStream stream = zipFile.getInputStream(zipFile.getEntry("emissions.txt")); Map emissionsData = readEmissions(stream, feedId); diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java index c0b4141b041..841c5252937 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java @@ -2,6 +2,7 @@ import dagger.Module; import jakarta.inject.Inject; +import java.io.File; import java.util.HashMap; import java.util.Map; import org.opentripplanner.graph_builder.ConfiguredDataSource; @@ -50,9 +51,9 @@ public void buildGraph() { for (ConfiguredDataSource gtfsData : dataSources.getGtfsConfiguredDatasource()) { if (gtfsData.dataSource().name().contains(".zip")) { - emissionsData = co2EmissionsDataReader.readGtfsZip(gtfsData.dataSource().path()); + emissionsData = co2EmissionsDataReader.readGtfsZip(new File(gtfsData.dataSource().uri())); } else { - emissionsData = co2EmissionsDataReader.readGtfs(gtfsData.dataSource().path()); + emissionsData = co2EmissionsDataReader.readGtfs(new File(gtfsData.dataSource().uri())); } } this.emissionsDataModel.setCo2Emissions(this.emissionsData); From 502a7ed5e042bd9da504fd0bea309cd0079a5925 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 31 Oct 2023 13:42:52 -0700 Subject: [PATCH 48/68] orca fares: fix water taxi fare calculation --- .../ext/fares/impl/OrcaFareServiceTest.java | 38 +++++++++++++------ .../ext/fares/impl/OrcaFareService.java | 14 +++---- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java index a08b50b083d..ee44d05aef1 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java @@ -7,6 +7,7 @@ import static org.opentripplanner.ext.fares.impl.OrcaFareService.KC_METRO_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.KITSAP_TRANSIT_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.PIERCE_COUNTY_TRANSIT_AGENCY_ID; +import static org.opentripplanner.ext.fares.impl.OrcaFareService.ROUTE_TYPE_FERRY; import static org.opentripplanner.ext.fares.impl.OrcaFareService.SKAGIT_TRANSIT_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.SOUND_TRANSIT_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.WASHINGTON_STATE_FERRIES_AGENCY_ID; @@ -340,12 +341,13 @@ void calculateFareForWSFPtToTahlequah() { * Single trip with Link Light Rail to ensure distance fare is calculated correctly. */ @Test - void calculateFareForLightRailLeg() { + void calculateFareForSTRail() { List rides = List.of( - getLeg(SOUND_TRANSIT_AGENCY_ID, "1-Line", 0, "Roosevelt Station", "Int'l Dist/Chinatown") + getLeg(SOUND_TRANSIT_AGENCY_ID, "1-Line", 0, "Roosevelt Station", "Int'l Dist/Chinatown"), + getLeg(SOUND_TRANSIT_AGENCY_ID, "S Line", 100, "King Street Station", "Auburn Station") ); - calculateFare(rides, regular, DEFAULT_TEST_RIDE_PRICE); - calculateFare(rides, FareType.senior, DEFAULT_TEST_RIDE_PRICE); + calculateFare(rides, regular, DEFAULT_TEST_RIDE_PRICE.times(2)); + calculateFare(rides, FareType.senior, DEFAULT_TEST_RIDE_PRICE.times(2)); calculateFare(rides, FareType.youth, Money.ZERO_USD); calculateFare(rides, FareType.electronicSpecial, ORCA_SPECIAL_FARE); calculateFare(rides, FareType.electronicRegular, DEFAULT_TEST_RIDE_PRICE); @@ -353,17 +355,31 @@ void calculateFareForLightRailLeg() { calculateFare(rides, FareType.electronicYouth, Money.ZERO_USD); } + /** + * Test King County Water Taxis + */ @Test - void calculateFareForSounderLeg() { + void calculateWaterTaxiFares() { List rides = List.of( - getLeg(SOUND_TRANSIT_AGENCY_ID, "S Line", 0, "King Street Station", "Auburn Station") + getLeg(KC_METRO_AGENCY_ID, "973", 1) ); - calculateFare(rides, regular, DEFAULT_TEST_RIDE_PRICE); - calculateFare(rides, FareType.senior, DEFAULT_TEST_RIDE_PRICE); + calculateFare(rides, regular, usDollars(5.75f)); + calculateFare(rides, FareType.senior, usDollars(5.75f)); calculateFare(rides, FareType.youth, Money.ZERO_USD); - calculateFare(rides, FareType.electronicSpecial, ORCA_SPECIAL_FARE); - calculateFare(rides, FareType.electronicRegular, DEFAULT_TEST_RIDE_PRICE); - calculateFare(rides, FareType.electronicSenior, ONE_DOLLAR); + calculateFare(rides, FareType.electronicSpecial, usDollars(3.75f)); + calculateFare(rides, FareType.electronicRegular, usDollars(5f)); + calculateFare(rides, FareType.electronicSenior, usDollars(2.50f)); + calculateFare(rides, FareType.electronicYouth, Money.ZERO_USD); + + rides = List.of( + getLeg(KC_METRO_AGENCY_ID, "973", 1) + ); + calculateFare(rides, regular, usDollars(5.75f)); + calculateFare(rides, FareType.senior, usDollars(5.75f)); + calculateFare(rides, FareType.youth, Money.ZERO_USD); + calculateFare(rides, FareType.electronicSpecial, usDollars(3.75f)); + calculateFare(rides, FareType.electronicRegular, usDollars(5f)); + calculateFare(rides, FareType.electronicSenior, usDollars(2.50f)); calculateFare(rides, FareType.electronicYouth, Money.ZERO_USD); } diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java index 3f9fe293f3d..06ca8d16ae0 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java @@ -134,13 +134,11 @@ static RideType getRideType(Leg leg) { } if ( - route.getGtfsType() == ROUTE_TYPE_FERRY && - routeLongNameFallBack(route).contains("Water Taxi: West Seattle") + "973".equals(route.getShortName()) ) { yield RideType.KC_WATER_TAXI_WEST_SEATTLE; } else if ( - route.getGtfsType() == ROUTE_TYPE_FERRY && - route.getDescription().contains("Water Taxi: Vashon Island") + "975".equals(route.getShortName()) ) { yield RideType.KC_WATER_TAXI_VASHON_ISLAND; } @@ -245,8 +243,8 @@ private Optional getRegularFare( ) { Route route = leg.getRoute(); return switch (rideType) { - case KC_WATER_TAXI_VASHON_ISLAND -> optionalUSD(5.75f); - case KC_WATER_TAXI_WEST_SEATTLE -> optionalUSD(5f); + case KC_WATER_TAXI_VASHON_ISLAND -> usesOrca(fareType) ? optionalUSD(5.75f) : optionalUSD(6.75f); + case KC_WATER_TAXI_WEST_SEATTLE -> usesOrca(fareType) ? optionalUSD(5f) : optionalUSD(5.75f); case KITSAP_TRANSIT_FAST_FERRY_EASTBOUND -> optionalUSD(2f); case KITSAP_TRANSIT_FAST_FERRY_WESTBOUND -> optionalUSD(10f); case WASHINGTON_STATE_FERRIES -> Optional.of( @@ -308,8 +306,8 @@ private Optional getSeniorFare( case KITSAP_TRANSIT_FAST_FERRY_EASTBOUND -> fareType.equals(FareType.electronicSenior) // Kitsap only provide discounted senior fare for orca. ? optionalUSD(1f) : optionalUSD(2f); - case KC_WATER_TAXI_VASHON_ISLAND -> optionalUSD(3f); - case KC_WATER_TAXI_WEST_SEATTLE -> optionalUSD(2.5f); + case KC_WATER_TAXI_VASHON_ISLAND -> usesOrca(fareType) ? optionalUSD(3f) : optionalUSD(6.75f); + case KC_WATER_TAXI_WEST_SEATTLE -> usesOrca(fareType) ? optionalUSD(2.5f) : optionalUSD(5.75f); case SOUND_TRANSIT, SOUND_TRANSIT_BUS, SOUND_TRANSIT_LINK, From 31e04d43975f3acf528f6b0dd6919dcd1d00c9bb Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 31 Oct 2023 14:20:36 -0700 Subject: [PATCH 49/68] remove unused import --- .../org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java index ee44d05aef1..fb4b78ec7e0 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java @@ -7,7 +7,6 @@ import static org.opentripplanner.ext.fares.impl.OrcaFareService.KC_METRO_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.KITSAP_TRANSIT_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.PIERCE_COUNTY_TRANSIT_AGENCY_ID; -import static org.opentripplanner.ext.fares.impl.OrcaFareService.ROUTE_TYPE_FERRY; import static org.opentripplanner.ext.fares.impl.OrcaFareService.SKAGIT_TRANSIT_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.SOUND_TRANSIT_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.WASHINGTON_STATE_FERRIES_AGENCY_ID; From 8eb34b50ed2395e231a8bd136d77ab457c94f039 Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 1 Nov 2023 08:56:32 +0200 Subject: [PATCH 50/68] Emissions issssue type and import fixes --- .../ext/emissions/Co2EmissionsDataReader.java | 14 +++++------ .../gtfs/generated/GraphQLDataFetchers.java | 24 +++++++++++++++---- .../apis/gtfs/generated/graphql-codegen.yml | 2 +- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java index 1e822cb3d10..f30fdb64114 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java @@ -38,12 +38,12 @@ public Map readGtfs(File directory) { try (InputStream feedInfoStream = new FileInputStream(directory + "/feed_info.txt")) { feedId = readFeedId(feedInfoStream); } catch (IOException e) { - issueStore.add("InvalidData", "Reading feed_info.txt failed."); + issueStore.add("InvalidEmissionData", "Reading feed_info.txt failed."); } try (InputStream stream = new FileInputStream(directory + "/emissions.txt")) { emissionsData = readEmissions(stream, feedId); } catch (IOException e) { - issueStore.add("InvalidData", "Reading emissions.txt failed."); + issueStore.add("InvalidEmissionData", "Reading emissions.txt failed."); } return emissionsData; } @@ -62,7 +62,7 @@ public Map readGtfsZip(File file) { zipFile.close(); return emissionsData; } catch (IOException e) { - issueStore.add("InvalidData", "Reading emissions data failed."); + issueStore.add("InvalidEmissionData", "Reading emissions data failed."); } return null; } @@ -80,14 +80,14 @@ private Map readEmissions(InputStream stream, String feedI if (avgCo2PerVehiclePerKmString.isEmpty()) { issueStore.add( - "InvalidData", + "InvalidEmissionData", "Value for avg_co2_per_vehicle_per_km is missing in the Emissions.txt for route %s", routeId ); } if (avgPassengerCountString.isEmpty()) { issueStore.add( - "InvalidData", + "InvalidEmissionData", "Value for avg_passenger_count is missing in the Emissions.txt for route %s", routeId ); @@ -114,7 +114,7 @@ private String readFeedId(InputStream stream) { reader.readRecord(); return reader.get("feed_id"); } catch (IOException e) { - issueStore.add("InvalidData", "Reading emissions data failed."); + issueStore.add("InvalidEmissionData", "Reading emissions data failed."); throw new RuntimeException(e); } } @@ -130,7 +130,7 @@ private Optional calculateEmissionsPerPassengerPerMeter( } if (avgPassengerCount <= 0 || avgCo2PerVehiclePerMeter < 0) { issueStore.add( - "InvalidData", + "InvalidEmissionData", "avgPassengerCount is 0 or less, but avgCo2PerVehiclePerMeter is nonzero or avgCo2PerVehiclePerMeter is negative for route %s", routeId ); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 15bc521290f..7f081b4faf6 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -336,6 +336,10 @@ public interface GraphQLDepartureRow { public DataFetcher> stoptimes(); } + public interface GraphQLEmissions { + public DataFetcher co2(); + } + /** A 'medium' that a fare product applies to, for example cash, 'Oyster Card' or 'DB Navigator App'. */ public interface GraphQLFareMedium { public DataFetcher id(); @@ -758,12 +762,24 @@ public interface GraphQLRentalVehicle { public DataFetcher vehicleType(); } + public interface GraphQLRentalVehicleEntityCounts { + public DataFetcher> byType(); + + public DataFetcher total(); + } + public interface GraphQLRentalVehicleType { public DataFetcher formFactor(); public DataFetcher propulsionType(); } + public interface GraphQLRentalVehicleTypeCount { + public DataFetcher count(); + + public DataFetcher vehicleType(); + } + /** An estimate for a ride on a hailed vehicle, like an Uber car. */ public interface GraphQLRideHailingEstimate { public DataFetcher arrival(); @@ -1163,6 +1179,10 @@ public interface GraphQLVehicleRentalStation { public DataFetcher allowPickupNow(); + public DataFetcher availableSpaces(); + + public DataFetcher availableVehicles(); + public DataFetcher capacity(); public DataFetcher id(); @@ -1186,10 +1206,6 @@ public interface GraphQLVehicleRentalStation { public DataFetcher stationId(); public DataFetcher vehiclesAvailable(); - - public DataFetcher availableVehicles(); - - public DataFetcher availableSpaces(); } public interface GraphQLVehicleRentalUris { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index 8da74674ba7..68901bdccef 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -25,7 +25,7 @@ config: Long: Long Polyline: String GeoJson: org.locationtech.jts.geom.Geometry - Grams: org.opentripplanner.model.plan.Grams + Grams: org.opentripplanner.framework.model.Grams Duration: java.time.Duration mappers: AbsoluteDirection: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLAbsoluteDirection#GraphQLAbsoluteDirection From 294ef8e9f2f8716974ad6389715443270d198713 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 1 Nov 2023 15:10:15 -0700 Subject: [PATCH 51/68] fix calculation for Whatcom fares --- .../ext/fares/impl/OrcaFareServiceTest.java | 19 +++++++++++++++++++ .../ext/fares/impl/OrcaFareService.java | 3 ++- .../transit/model/basic/Money.java | 5 +++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java index fb4b78ec7e0..6f44bfdcf8b 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java @@ -10,6 +10,7 @@ import static org.opentripplanner.ext.fares.impl.OrcaFareService.SKAGIT_TRANSIT_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.SOUND_TRANSIT_AGENCY_ID; import static org.opentripplanner.ext.fares.impl.OrcaFareService.WASHINGTON_STATE_FERRIES_AGENCY_ID; +import static org.opentripplanner.ext.fares.impl.OrcaFareService.WHATCOM_AGENCY_ID; import static org.opentripplanner.model.plan.PlanTestConstants.T11_00; import static org.opentripplanner.model.plan.PlanTestConstants.T11_12; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; @@ -453,6 +454,24 @@ void calculateTransferExtension() { calculateFare(rides, FareType.electronicYouth, Money.ZERO_USD); } + /** + * Tests fares from non ORCA accepting agencies + */ + @Test + void testNonOrcaAgencies() { + List rides = List.of( + getLeg(SKAGIT_TRANSIT_AGENCY_ID, 0, "80X"), + getLeg(WHATCOM_AGENCY_ID, 0, "80X") + ); + + calculateFare(rides, regular, DEFAULT_TEST_RIDE_PRICE.times(2)); + calculateFare(rides, FareType.senior, usDollars(0.50f).times(2)); + // TODO: Check that these are undefined, not zero + calculateFare(rides, FareType.youth, ZERO_USD); + calculateFare(rides, FareType.electronicSpecial, ZERO_USD); + calculateFare(rides, FareType.electronicRegular, ZERO_USD); + } + static Stream allTypes() { return Arrays.stream(FareType.values()).map(Arguments::of); } diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java index 06ca8d16ae0..3628ae864c6 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java @@ -285,6 +285,7 @@ private Optional getLiftFare(RideType rideType, Money defaultFare, Route ); case KITSAP_TRANSIT_FAST_FERRY_EASTBOUND -> optionalUSD((1f)); case KITSAP_TRANSIT_FAST_FERRY_WESTBOUND -> optionalUSD((5f)); + case SKAGIT_LOCAL, SKAGIT_CROSS_COUNTY, WHATCOM_CROSS_COUNTY, WHATCOM_LOCAL -> Optional.empty(); default -> Optional.of(defaultFare); }; } @@ -326,7 +327,7 @@ private Optional getSeniorFare( case WASHINGTON_STATE_FERRIES -> Optional.of( getWashingtonStateFerriesFare(route.getLongName(), fareType, defaultFare) ); - case WHATCOM_CROSS_COUNTY, SKAGIT_CROSS_COUNTY -> optionalUSD(1f); + case WHATCOM_CROSS_COUNTY, SKAGIT_CROSS_COUNTY -> Optional.of(defaultFare.half()); default -> Optional.of(defaultFare); }; } diff --git a/src/main/java/org/opentripplanner/transit/model/basic/Money.java b/src/main/java/org/opentripplanner/transit/model/basic/Money.java index e6cb8f53887..d1dfa158664 100644 --- a/src/main/java/org/opentripplanner/transit/model/basic/Money.java +++ b/src/main/java/org/opentripplanner/transit/model/basic/Money.java @@ -140,6 +140,11 @@ public Money plus(Money other) { return op(other, o -> new Money(currency, amount + o.amount)); } + /** + * Returns half this instance's amount + */ + public Money half() { return new Money(currency, amount / 2); } + /** * Multiplies the amount with the multiplicator. */ From aa5f2352551e68c4e5b3ec4b5fa9098608d11120 Mon Sep 17 00:00:00 2001 From: sharhio <113033056+sharhio@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:12:49 +0200 Subject: [PATCH 52/68] Update src/main/java/org/opentripplanner/model/plan/Itinerary.java Co-authored-by: Thomas Gran --- src/main/java/org/opentripplanner/model/plan/Itinerary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index 4f63f538e89..4fa4707f590 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -266,7 +266,7 @@ public String toString() { .addNum("elevationGained", elevationGained, 0.0) .addCol("legs", legs) .addObj("fare", fare) - .addObj("emissions", emissionsPerPerson) + .addObj("emissionsPerPerson", emissionsPerPerson) .toString(); } From 291bd790bee5a2253e95291e8d47a1bc9c31ae6a Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Thu, 2 Nov 2023 11:21:25 -0700 Subject: [PATCH 53/68] add test for new Money.half() method --- .../java/org/opentripplanner/transit/model/basic/Money.java | 3 ++- .../java/org/opentripplanner/routing/core/MoneyTest.java | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/transit/model/basic/Money.java b/src/main/java/org/opentripplanner/transit/model/basic/Money.java index d1dfa158664..6689438eed0 100644 --- a/src/main/java/org/opentripplanner/transit/model/basic/Money.java +++ b/src/main/java/org/opentripplanner/transit/model/basic/Money.java @@ -142,8 +142,9 @@ public Money plus(Money other) { /** * Returns half this instance's amount + * Amounts in minor currency unit is rounded to nearest integer, so $0.99/2 becomes $0.50 */ - public Money half() { return new Money(currency, amount / 2); } + public Money half() { return new Money(currency, IntUtils.round(amount / 2f)); } /** * Multiplies the amount with the multiplicator. diff --git a/src/test/java/org/opentripplanner/routing/core/MoneyTest.java b/src/test/java/org/opentripplanner/routing/core/MoneyTest.java index 5d3ed8a1b46..2323b396d5c 100644 --- a/src/test/java/org/opentripplanner/routing/core/MoneyTest.java +++ b/src/test/java/org/opentripplanner/routing/core/MoneyTest.java @@ -81,6 +81,12 @@ void times() { assertEquals(Money.usDollars(4), oneDollar.times(4)); } + @Test + void half() { + assertEquals(Money.usDollars(0.50f), Money.usDollars(0.99f).half()); + assertEquals(Money.usDollars(0.38f), Money.usDollars(0.75f).half()); + } + @Test void greaterThan() { assertTrue(twoDollars.greaterThan(oneDollar)); From 304b86dcb681fbc3e05318eba5ab9b3fe7f49161 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Thu, 2 Nov 2023 11:59:09 -0700 Subject: [PATCH 54/68] run formatter, extract constants --- .../ext/fares/impl/OrcaFareServiceTest.java | 25 +++++++++---------- .../ext/fares/impl/OrcaFareService.java | 23 +++++++++-------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java index 6f44bfdcf8b..22dfdd38303 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java @@ -56,6 +56,8 @@ public class OrcaFareServiceTest { private static final Money FERRY_FARE = usDollars(6.50f); private static final Money HALF_FERRY_FARE = usDollars(3.25f); private static final Money ORCA_SPECIAL_FARE = usDollars(1.00f); + public static final Money VASHON_WATER_TAXI_CASH_FARE = usDollars(6.75f); + public static final Money WEST_SEATTLE_WATER_TAXI_CASH_FARE = usDollars(5.75f); private static final String FEED_ID = "A"; private static TestOrcaFareService orcaFareService; public static final Money DEFAULT_TEST_RIDE_PRICE = usDollars(3.49f); @@ -360,26 +362,23 @@ void calculateFareForSTRail() { */ @Test void calculateWaterTaxiFares() { - List rides = List.of( - getLeg(KC_METRO_AGENCY_ID, "973", 1) - ); - calculateFare(rides, regular, usDollars(5.75f)); - calculateFare(rides, FareType.senior, usDollars(5.75f)); + List rides = List.of(getLeg(KC_METRO_AGENCY_ID, "973", 1)); + calculateFare(rides, regular, WEST_SEATTLE_WATER_TAXI_CASH_FARE); + calculateFare(rides, FareType.senior, WEST_SEATTLE_WATER_TAXI_CASH_FARE); calculateFare(rides, FareType.youth, Money.ZERO_USD); calculateFare(rides, FareType.electronicSpecial, usDollars(3.75f)); calculateFare(rides, FareType.electronicRegular, usDollars(5f)); calculateFare(rides, FareType.electronicSenior, usDollars(2.50f)); calculateFare(rides, FareType.electronicYouth, Money.ZERO_USD); - rides = List.of( - getLeg(KC_METRO_AGENCY_ID, "973", 1) - ); - calculateFare(rides, regular, usDollars(5.75f)); - calculateFare(rides, FareType.senior, usDollars(5.75f)); + rides = List.of(getLeg(KC_METRO_AGENCY_ID, "975", 1)); + + calculateFare(rides, regular, VASHON_WATER_TAXI_CASH_FARE); + calculateFare(rides, FareType.senior, VASHON_WATER_TAXI_CASH_FARE); calculateFare(rides, FareType.youth, Money.ZERO_USD); - calculateFare(rides, FareType.electronicSpecial, usDollars(3.75f)); - calculateFare(rides, FareType.electronicRegular, usDollars(5f)); - calculateFare(rides, FareType.electronicSenior, usDollars(2.50f)); + calculateFare(rides, FareType.electronicSpecial, usDollars(4.50f)); + calculateFare(rides, FareType.electronicRegular, usDollars(5.75f)); + calculateFare(rides, FareType.electronicSenior, usDollars(3f)); calculateFare(rides, FareType.electronicYouth, Money.ZERO_USD); } diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java index 3628ae864c6..b8c2de89a4e 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java @@ -133,13 +133,9 @@ static RideType getRideType(Leg leg) { // Lettered routes exist, are not an error. } - if ( - "973".equals(route.getShortName()) - ) { + if ("973".equals(route.getShortName())) { yield RideType.KC_WATER_TAXI_WEST_SEATTLE; - } else if ( - "975".equals(route.getShortName()) - ) { + } else if ("975".equals(route.getShortName())) { yield RideType.KC_WATER_TAXI_VASHON_ISLAND; } yield RideType.KC_METRO; @@ -243,8 +239,10 @@ private Optional getRegularFare( ) { Route route = leg.getRoute(); return switch (rideType) { - case KC_WATER_TAXI_VASHON_ISLAND -> usesOrca(fareType) ? optionalUSD(5.75f) : optionalUSD(6.75f); - case KC_WATER_TAXI_WEST_SEATTLE -> usesOrca(fareType) ? optionalUSD(5f) : optionalUSD(5.75f); + case KC_WATER_TAXI_VASHON_ISLAND -> usesOrca(fareType) + ? optionalUSD(5.75f) + : optionalUSD(6.75f); + case KC_WATER_TAXI_WEST_SEATTLE -> usesOrca(fareType) ? optionalUSD(5f) : optionalUSD(5.75f); case KITSAP_TRANSIT_FAST_FERRY_EASTBOUND -> optionalUSD(2f); case KITSAP_TRANSIT_FAST_FERRY_WESTBOUND -> optionalUSD(10f); case WASHINGTON_STATE_FERRIES -> Optional.of( @@ -285,7 +283,10 @@ private Optional getLiftFare(RideType rideType, Money defaultFare, Route ); case KITSAP_TRANSIT_FAST_FERRY_EASTBOUND -> optionalUSD((1f)); case KITSAP_TRANSIT_FAST_FERRY_WESTBOUND -> optionalUSD((5f)); - case SKAGIT_LOCAL, SKAGIT_CROSS_COUNTY, WHATCOM_CROSS_COUNTY, WHATCOM_LOCAL -> Optional.empty(); + case SKAGIT_LOCAL, + SKAGIT_CROSS_COUNTY, + WHATCOM_CROSS_COUNTY, + WHATCOM_LOCAL -> Optional.empty(); default -> Optional.of(defaultFare); }; } @@ -308,7 +309,9 @@ private Optional getSeniorFare( ? optionalUSD(1f) : optionalUSD(2f); case KC_WATER_TAXI_VASHON_ISLAND -> usesOrca(fareType) ? optionalUSD(3f) : optionalUSD(6.75f); - case KC_WATER_TAXI_WEST_SEATTLE -> usesOrca(fareType) ? optionalUSD(2.5f) : optionalUSD(5.75f); + case KC_WATER_TAXI_WEST_SEATTLE -> usesOrca(fareType) + ? optionalUSD(2.5f) + : optionalUSD(5.75f); case SOUND_TRANSIT, SOUND_TRANSIT_BUS, SOUND_TRANSIT_LINK, From 5ca2c88f2e846d2399e0d15136479f3d4aadf0c9 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Thu, 2 Nov 2023 11:59:45 -0700 Subject: [PATCH 55/68] format Money.java --- .../java/org/opentripplanner/transit/model/basic/Money.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/transit/model/basic/Money.java b/src/main/java/org/opentripplanner/transit/model/basic/Money.java index 6689438eed0..e44994d1105 100644 --- a/src/main/java/org/opentripplanner/transit/model/basic/Money.java +++ b/src/main/java/org/opentripplanner/transit/model/basic/Money.java @@ -144,7 +144,9 @@ public Money plus(Money other) { * Returns half this instance's amount * Amounts in minor currency unit is rounded to nearest integer, so $0.99/2 becomes $0.50 */ - public Money half() { return new Money(currency, IntUtils.round(amount / 2f)); } + public Money half() { + return new Money(currency, IntUtils.round(amount / 2f)); + } /** * Multiplies the amount with the multiplicator. From a9b015180565038c388725b50edfa124b1dbefa8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 01:37:46 +0000 Subject: [PATCH 56/68] chore(deps): update dependency org.mockito:mockito-core to v5.7.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f4c371f4578..3976440d506 100644 --- a/pom.xml +++ b/pom.xml @@ -739,7 +739,7 @@ org.mockito mockito-core - 5.6.0 + 5.7.0 test From 35ebd1a67c080c2876930816374adca49d025ef9 Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Thu, 2 Nov 2023 16:12:27 +0100 Subject: [PATCH 57/68] Add logging of siri errors to SiriAzureETUpdater --- .../updater/azure/SiriAzureETUpdater.java | 30 ++++++++++++++++++- .../azure/SiriAzureETUpdaterParameters.java | 8 ++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java index ef138264999..3fba2edd93f 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.siri.updater.azure; +import static net.logstash.logback.argument.StructuredArguments.keyValue; + import com.azure.messaging.servicebus.ServiceBusErrorContext; import com.azure.messaging.servicebus.ServiceBusReceivedMessageContext; import jakarta.xml.bind.JAXBException; @@ -14,11 +16,13 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.xml.stream.XMLStreamException; import org.apache.hc.core5.net.URIBuilder; import org.opentripplanner.ext.siri.SiriTimetableSnapshotSource; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.trip.metrics.TripUpdateMetrics; import org.rutebanken.siri20.util.SiriXml; @@ -106,13 +110,15 @@ private void processMessage(String message, String id) { } super.saveResultOnGraph.execute((graph, transitModel) -> { - snapshotSource.applyEstimatedTimetable( + var result = snapshotSource.applyEstimatedTimetable( fuzzyTripMatcher(), entityResolver(), feedId, false, updates ); + logErrors(result); + recordMetrics.accept(result); }); } catch (JAXBException | XMLStreamException e) { LOG.error(e.getLocalizedMessage(), e); @@ -138,6 +144,7 @@ private void processHistory(String message, String id) { false, updates ); + logErrors(result); recordMetrics.accept(result); setPrimed(true); @@ -174,4 +181,25 @@ private List getUpdates(String message, Str return siri.getServiceDelivery().getEstimatedTimetableDeliveries(); } + + private void logErrors(UpdateResult updateResult) { + if (updateResult.failed() == 0) { + return; + } + var errorIndex = updateResult.failures(); + errorIndex + .keySet() + .forEach(key -> { + var value = errorIndex.get(key); + var tripIds = value.stream().map(UpdateError::debugId).collect(Collectors.toSet()); + LOG.warn( + "[{} {}] {} failures of {}: {}", + keyValue("feedId", feedId), + keyValue("type", "siri-et"), + value.size(), + keyValue("errorType", key), + tripIds + ); + }); + } } diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdaterParameters.java index a1ee8c5a79e..e63615dd893 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdaterParameters.java @@ -1,5 +1,6 @@ package org.opentripplanner.ext.siri.updater.azure; +import com.azure.core.amqp.implementation.ConnectionStringProperties; import java.time.LocalDate; import org.opentripplanner.updater.trip.UrlUpdaterParameters; @@ -23,6 +24,11 @@ public void setFromDateTime(LocalDate fromDateTime) { @Override public String url() { - return getDataInitializationUrl(); + var url = getServiceBusUrl(); + try { + return new ConnectionStringProperties(url).getEndpoint().toString(); + } catch (IllegalArgumentException e) { + return url; + } } } From b563544a8e48602053833cab8d22e19b6f160057 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 02:13:56 +0000 Subject: [PATCH 58/68] chore(deps): update junit5 monorepo to v5.10.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3976440d506..e3930f41687 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 2.48.1 2.15.3 3.1.3 - 5.10.0 + 5.10.1 1.11.5 5.5.3 1.4.11 From dab69ff3a88a2efa9ea83345d4168167a98164f7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 02:14:01 +0000 Subject: [PATCH 59/68] chore(deps): update dependency com.tngtech.archunit:archunit to v1.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e3930f41687..132c5c9f86e 100644 --- a/pom.xml +++ b/pom.xml @@ -733,7 +733,7 @@ com.tngtech.archunit archunit - 1.1.0 + 1.2.0 test From fb85e6d3b1fd4214aaa6c60d5c9236057c35a279 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 6 Nov 2023 10:12:20 +0100 Subject: [PATCH 60/68] Update gbfs-java-model monthly [ci skip] --- renovate.json5 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/renovate.json5 b/renovate.json5 index b600b9593d6..836ba065662 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -49,7 +49,8 @@ "@graphql-codegen/java-resolvers", "graphql", "io.micrometer:micrometer-registry-prometheus", - "io.micrometer:micrometer-registry-influx" + "io.micrometer:micrometer-registry-influx", + "org.entur.gbfs:gbfs-java-model" ], // we don't use the 'monthly' preset because that only fires on the first day of the month // when there might already other PRs open From a842ae88f204602133ed1fb2a7355a7ebd2afc78 Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Mon, 6 Nov 2023 10:47:13 +0100 Subject: [PATCH 61/68] Use ResultLogger in SiriAzureETUpdater --- .../updater/azure/SiriAzureETUpdater.java | 26 ++----------- .../updater/spi/ResultLogger.java | 38 +++++++++++-------- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java index 3fba2edd93f..921657bb8f1 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java @@ -22,6 +22,7 @@ import org.opentripplanner.ext.siri.SiriTimetableSnapshotSource; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.spi.ResultLogger; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.trip.metrics.TripUpdateMetrics; @@ -117,7 +118,7 @@ private void processMessage(String message, String id) { false, updates ); - logErrors(result); + ResultLogger.logUpdateResultErrors(feedId, "siri-et", result); recordMetrics.accept(result); }); } catch (JAXBException | XMLStreamException e) { @@ -144,7 +145,7 @@ private void processHistory(String message, String id) { false, updates ); - logErrors(result); + ResultLogger.logUpdateResultErrors(feedId, "siri-et", result); recordMetrics.accept(result); setPrimed(true); @@ -181,25 +182,4 @@ private List getUpdates(String message, Str return siri.getServiceDelivery().getEstimatedTimetableDeliveries(); } - - private void logErrors(UpdateResult updateResult) { - if (updateResult.failed() == 0) { - return; - } - var errorIndex = updateResult.failures(); - errorIndex - .keySet() - .forEach(key -> { - var value = errorIndex.get(key); - var tripIds = value.stream().map(UpdateError::debugId).collect(Collectors.toSet()); - LOG.warn( - "[{} {}] {} failures of {}: {}", - keyValue("feedId", feedId), - keyValue("type", "siri-et"), - value.size(), - keyValue("errorType", key), - tripIds - ); - }); - } } diff --git a/src/main/java/org/opentripplanner/updater/spi/ResultLogger.java b/src/main/java/org/opentripplanner/updater/spi/ResultLogger.java index 431f630c578..97af8d650bb 100644 --- a/src/main/java/org/opentripplanner/updater/spi/ResultLogger.java +++ b/src/main/java/org/opentripplanner/updater/spi/ResultLogger.java @@ -27,24 +27,30 @@ public static void logUpdateResult(String feedId, String type, UpdateResult upda DoubleUtils.roundTo2Decimals((double) updateResult.successful() / totalUpdates * 100) ); - var errorIndex = updateResult.failures(); - - errorIndex - .keySet() - .forEach(key -> { - var value = errorIndex.get(key); - var tripIds = value.stream().map(UpdateError::debugId).collect(Collectors.toSet()); - LOG.warn( - "[{} {}] {} failures of {}: {}", - keyValue("feedId", feedId), - keyValue("type", type), - value.size(), - keyValue("errorType", key), - tripIds - ); - }); + logUpdateResultErrors(feedId, type, updateResult); } else { LOG.info("[feedId={}, type={}] Feed did not contain any updates", feedId, type); } } + + public static void logUpdateResultErrors(String feedId, String type, UpdateResult updateResult) { + if (updateResult.failed() == 0) { + return; + } + var errorIndex = updateResult.failures(); + errorIndex + .keySet() + .forEach(key -> { + var value = errorIndex.get(key); + var tripIds = value.stream().map(UpdateError::debugId).collect(Collectors.toSet()); + LOG.warn( + "[{} {}] {} failures of {}: {}", + keyValue("feedId", feedId), + keyValue("type", type), + value.size(), + keyValue("errorType", key), + tripIds + ); + }); + } } From 0f331e993b0f45e70c89470dbba507aa20a379c1 Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 6 Nov 2023 13:13:34 +0200 Subject: [PATCH 62/68] Emissions model config and doc generation fix --- docs/BuildConfiguration.md | 16 +--------------- .../graph_builder/GraphBuilder.java | 15 ++++++++++----- .../module/configure/GraphBuilderFactory.java | 2 +- .../module/configure/GraphBuilderModules.java | 3 ++- .../standalone/api/OtpServerRequestContext.java | 2 ++ .../configure/ConstructApplicationFactory.java | 2 ++ .../generate/doc/BuildConfigurationDocTest.java | 1 + 7 files changed, 19 insertions(+), 22 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 6233939c2ab..f66decf4fad 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -55,9 +55,7 @@ Sections follow that describe particular settings in more depth. | demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | |    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | | [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | -| [emissions](#emissions) | `object` | Emissions configuration. | *Optional* | | na | -|    carAvgCo2PerKm | `integer` | The average CO₂ emissions of a car in grams per kilometer. | *Optional* | `170` | na | -|    carAvgOccupancy | `double` | The average number of passengers in a car. | *Optional* | `1.3` | na | +| [emissions](sandbox/Emissions.md) | `object` | Emissions configuration. | *Optional* | | na | | [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | | gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | |    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | @@ -726,18 +724,6 @@ for the next graph build operation. You should add the `--cache ` com to specify your NED tile cache location. -

emissions

- -**Since version:** `na` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` -**Path:** / - -Emissions configuration. - -By specifying the average CO₂ emissions of a car in grams per kilometer as well as -the average number of passengers in a car the program is able to to perform emission -calculations for car travel. - -

discardMinTransferTimes

**Since version:** `2.3` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `false` diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index 93945ce4985..5b322f735bf 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OtpAppException; @@ -61,7 +62,7 @@ public static GraphBuilder create( Graph graph, TransitModel transitModel, WorldEnvelopeRepository worldEnvelopeRepository, - EmissionsDataModel emissionsDataModel, + @Nullable EmissionsDataModel emissionsDataModel, boolean loadStreetGraph, boolean saveStreetGraph ) { @@ -73,16 +74,20 @@ public static GraphBuilder create( transitModel.initTimeZone(config.transitModelTimeZone); - var factory = DaggerGraphBuilderFactory + var builder = DaggerGraphBuilderFactory .builder() .config(config) .graph(graph) .transitModel(transitModel) .worldEnvelopeRepository(worldEnvelopeRepository) .dataSources(dataSources) - .timeZoneId(transitModel.getTimeZone()) - .emissionsDataModel(emissionsDataModel) - .build(); + .timeZoneId(transitModel.getTimeZone()); + + if (OTPFeature.Co2Emissions.isOn()) { + builder.emissionsDataModel(emissionsDataModel); + } + + var factory = builder.build(); var graphBuilder = factory.graphBuilder(); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java index d22a300ef1f..0b2a46e165e 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java @@ -79,6 +79,6 @@ interface Builder { GraphBuilderFactory build(); @BindsInstance - Builder emissionsDataModel(EmissionsDataModel emissionsDataModel); + Builder emissionsDataModel(@Nullable EmissionsDataModel emissionsDataModel); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index faddf4af3b2..755b7725941 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -8,6 +8,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.ext.dataoverlay.EdgeUpdaterModule; import org.opentripplanner.ext.dataoverlay.configure.DataOverlayFactory; @@ -113,7 +114,7 @@ static GtfsModule provideGtfsModule( static EmissionsModule provideEmissionsModule( GraphBuilderDataSources dataSources, BuildConfig config, - EmissionsDataModel emissionsDataModel, + @Nullable EmissionsDataModel emissionsDataModel, DataImportIssueStore issueStore ) { return new EmissionsModule(dataSources, config, emissionsDataModel, issueStore); diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index c6d7e25a002..db105338736 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -3,6 +3,7 @@ import io.micrometer.core.instrument.MeterRegistry; import java.util.List; import java.util.Locale; +import javax.annotation.Nullable; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext; import org.opentripplanner.ext.emissions.EmissionsService; @@ -95,6 +96,7 @@ public interface OtpServerRequestContext { MeterRegistry meterRegistry(); + @Nullable EmissionsService emissionsService(); /** Inspector/debug services */ diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index 43736d3f65c..eb286ea5843 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -61,6 +61,8 @@ public interface ConstructApplicationFactory { VehicleRentalRepository vehicleRentalRepository(); VehicleRentalService vehicleRentalService(); DataImportIssueSummary dataImportIssueSummary(); + + @Nullable EmissionsDataModel emissionsDataModel(); @Nullable diff --git a/src/test/java/org/opentripplanner/generate/doc/BuildConfigurationDocTest.java b/src/test/java/org/opentripplanner/generate/doc/BuildConfigurationDocTest.java index 231ce70407a..287b0145292 100644 --- a/src/test/java/org/opentripplanner/generate/doc/BuildConfigurationDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/BuildConfigurationDocTest.java @@ -34,6 +34,7 @@ public class BuildConfigurationDocTest { .skip("dataOverlay", "sandbox/DataOverlay.md") .skip("fares", "sandbox/Fares.md") .skip("transferRequests", "RouteRequest.md") + .skip("emissions", "sandbox/Emissions.md") .build(); /** From eaf94c3e3d3127b4ac414655c374868ee19a04ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:00:31 +0000 Subject: [PATCH 63/68] fix(deps): update dependency org.entur.gbfs:gbfs-java-model to v3.0.13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 132c5c9f86e..727f6fb05cf 100644 --- a/pom.xml +++ b/pom.xml @@ -714,7 +714,7 @@ org.entur.gbfs gbfs-java-model - 3.0.11 + 3.0.13 From 0ca68ea1d958da7e392d12eaa05a1939942b516a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 23:11:19 +0000 Subject: [PATCH 64/68] fix(deps): update dependency com.google.guava:guava to v32.1.3-jre --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 132c5c9f86e..c25ba757bb5 100644 --- a/pom.xml +++ b/pom.xml @@ -765,7 +765,7 @@ com.google.guava guava - 32.1.2-jre + 32.1.3-jre From d0090060cb3ad4e91261b29338b2d5c711684199 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 23:11:13 +0000 Subject: [PATCH 65/68] chore(deps): update dependency org.apache.maven.plugins:maven-surefire-plugin to v3.2.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 132c5c9f86e..e5a04bd5194 100644 --- a/pom.xml +++ b/pom.xml @@ -242,7 +242,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.1 + 3.2.2 me.fabriciorby From 76e9e274c59dd887901be91708dacd8ba574d473 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:56:39 +0000 Subject: [PATCH 66/68] fix(deps): update dependency com.google.cloud:libraries-bom to v26.26.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e5a04bd5194..963884e7ffa 100644 --- a/pom.xml +++ b/pom.xml @@ -572,7 +572,7 @@ com.google.cloud libraries-bom - 26.24.0 + 26.26.0 pom import From 13fe69f854702ded68a5d98686066ce8b1886ec2 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 7 Nov 2023 10:39:42 +0000 Subject: [PATCH 67/68] Add changelog entry for #5278 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 238caddcf4b..13157fc1180 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -33,6 +33,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Ignore negative travel-times in Raptor [#5443](https://github.com/opentripplanner/OpenTripPlanner/pull/5443) - Fix sort order bug in optimized transfers [#5446](https://github.com/opentripplanner/OpenTripPlanner/pull/5446) - Siri file loader [#5460](https://github.com/opentripplanner/OpenTripPlanner/pull/5460) +- Calculate CO₂ emissions of itineraries [#5278](https://github.com/opentripplanner/OpenTripPlanner/pull/5278) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 70bf18a8bbac39e7bfcd0401f8a95d863099b425 Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Tue, 7 Nov 2023 10:40:14 +0000 Subject: [PATCH 68/68] Bump serialization version id for #5278 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0ab75c52039..16dac4d6cc8 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 123 + 124 30.0 2.48.1