Utah Transit Authority#

In this example, we'll predict the energy consumption for some trips operated by the Utah Transit Authority (UTA) in Salt Lake City. This requires specifying the GTFS data we are analyzing, map-matching it to the road network, and running a RouteE-Compass model to produce energy estimates.

This example uses the GTFSEnergyPredictor class, which provides a clean, extensible API for transit energy prediction.

from pathlib import Path
from routee.transit import GTFSEnergyPredictor, sample_inputs_path

# Specify input data location
input_directory = sample_inputs_path() / "saltlake/gtfs"
output_directory = Path("reports/saltlake")

Quick Start: Using the run() Method#

For most use cases, the run() method provides the simplest way to perform the complete energy prediction workflow. This single method call chains together all processing steps and returns trip-level energy predictions.

We'll analyze routes 806 and 807 on August 2nd, 2023, using the Battery Electric Bus model. We'll include deadhead trips and estimated HVAC energy.

predictor = GTFSEnergyPredictor(
    gtfs_path=input_directory,
    vehicle_models=["Transit_Bus_Battery_Electric"],
    output_dir=output_directory,
)

trip_results = predictor.run(
    date="2023/08/02",
    routes=["806", "807"],
    add_depot_deadhead=True,
    add_mid_block_deadhead=True,
    add_hvac=True,
    save_results=False,
)
Downloaded: G4900350.csv
Downloaded: G4900490.csv
Downloaded: G4900450.csv
Downloaded: G4900110.csv
Downloaded: G4900570.csv
Downloaded: G4900030.csv
Downloaded: G4900430.csv
INFO:routee.transit.predictor:Initialized GTFSEnergyPredictor for /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/routee/transit/resources/sample_inputs/saltlake/gtfs
INFO:routee.transit.predictor:Loading GTFS data...
INFO:routee.transit.predictor:Feed includes 1 agencies: ['Utah Transit Authority']. Total trips: 12037, shapes: 238
INFO:routee.transit.predictor:Loaded 12037 trips and 199 shapes
INFO:routee.transit.predictor:Filtering trips (date=2023/08/02, routes=['806', '807'])...
INFO:routee.transit.predictor:Filtered to 16 trips and 4 shapes
INFO:routee.transit.predictor:Preparing mid-block deadhead trips...
INFO:routee.transit.predictor:Preparing depot deadhead trips...
INFO:routee.transit.predictor:Building CompassApp from bounding box: (-112.03101, 40.22523, -111.65814, 40.482409999999994)
INFO:nrel.routee.compass.io.generate_dataset:running pipeline import with phases: [['GRAPH', 'CONFIG', 'POWERTRAIN']]
INFO:nrel.routee.compass.io.generate_dataset:processing graph topology and speeds
INFO:nrel.routee.compass.io.generate_dataset:adding grade information
INFO:nrel.routee.compass.io.utils:downloading n41w113
INFO:nrel.routee.compass.io.utils:downloading n41w112
INFO:nrel.routee.compass.io.generate_dataset:processing vertices
INFO:nrel.routee.compass.io.generate_dataset:processing edges
INFO:nrel.routee.compass.io.generate_dataset:writing vertex files
INFO:nrel.routee.compass.io.generate_dataset:writing edge files
INFO:nrel.routee.compass.io.generate_dataset:writing edge attribute files
INFO:nrel.routee.compass.io.generate_dataset:copying default configuration TOML files
INFO:nrel.routee.compass.io.generate_dataset:downloading the default RouteE Powertrain models
INFO:nrel.routee.compass.io.generate_dataset:copying vehicle configuration files
INFO:nrel.routee.compass.io.generate_dataset:running 2 dataset generation hooks
INFO:gtfs_processing:Wrote 109260 stop-edge mappings to reports/saltlake/compass_app/gtfs_stops.csv
INFO:gtfs_processing:Copied transit_energy.toml to reports/saltlake/compass_app/transit_energy.toml
INFO:routee.transit.predictor:CompassApp initialized
graph edge list 0: /home/runner/work/routee-transit/routee-transit/docs/examples/reports/saltlake/compass_app/edges-compass.csv.gz: 46796it [00:00, 1781413.62it/s]
building adjacencies: 100%|██████████| 46796/46796 [00:00<00:00, 5749321.50it/s]
INFO:routee.transit.predictor:Routing mid-block deadhead trips...
INFO:routee.transit.deadhead_router:Deadhead routing: 7 trips reduced to 3 unique O-D pairs


applying input plugin 1: 100%|██████████| 3/3 [00:00<00:00, 9564.68it/s]

applying input plugin 2: 100%|██████████| 3/3 [00:00<00:00, 231356.53it/s]

search: 100%|██████████| 3/3 [00:00<00:00, 75.75it/s]
INFO:routee.transit.predictor:Added 7 mid-block deadhead trips
INFO:routee.transit.predictor:Routing depot deadhead trips...
INFO:routee.transit.deadhead_router:Deadhead routing: 9 trips reduced to 3 unique O-D pairs


applying input plugin 1: 100%|██████████| 3/3 [00:00<00:00, 38702.69it/s]

applying input plugin 2: 100%|██████████| 3/3 [00:00<00:00, 253614.00it/s]

search: 100%|██████████| 3/3 [00:00<00:00, 28.49it/s]
INFO:routee.transit.deadhead_router:Deadhead routing: 9 trips reduced to 3 unique O-D pairs


applying input plugin 1: 100%|██████████| 3/3 [00:00<00:00, 60129.88it/s]

applying input plugin 2: 100%|██████████| 3/3 [00:00<00:00, 249438.77it/s]

search: 100%|██████████| 3/3 [00:00<00:00, 57.59it/s]
INFO:routee.transit.predictor:Added 18 depot deadhead trips
INFO:routee.transit.predictor:Predicting energy for 1 vehicle model(s)...
INFO:routee.transit.predictor:Running map matching for 13 shapes...
map matching:  15%|█▅        |  2/13 [00:00<00:00, 16.50it/s]
map matching: 100%|██████████| 13/13 [00:01<00:00, 7.33it/s]
INFO:routee.transit.predictor:Running energy prediction via CompassApp for: Transit_Bus_Battery_Electric
calculating paths: 100%|██████████| 13/13 [00:00<00:00, 894.52it/s]
INFO:routee.transit.predictor:Adding HVAC energy impacts...
INFO:routee.transit.predictor:Energy prediction complete

The run() method automatically performs all these steps:

  1. Loads the GTFS feed

  2. Filters trips by date and routes

  3. Adds mid-block deadhead trips (between consecutive trips)

  4. Adds depot deadhead trips (to/from depot)

  5. Matches shapes to OpenStreetMap road network and adds road grade (via RouteE-Compass)

  6. Predicts energy consumption with RouteE-Compass

  7. Adds estimated HVAC energy impacts

  8. Saves results to CSV files

Let's examine the results. The columns include information about the energy predictions made (including any HVAC component) as well as GTFS fields that apply to each trip, and a description of the weather scenario considered:

trip_results.columns
trip_results[["trip_id", "vehicle", "scenario", "energy_used", "miles"]].head()
trip_id vehicle scenario energy_used miles
0 5181298_to_5181301 Transit_Bus_Battery_Electric median 16.967052 15.491856
1 5181298_to_5181301 Transit_Bus_Battery_Electric summer 16.025047 15.491856
2 5181298_to_5181301 Transit_Bus_Battery_Electric winter 44.538575 15.491856
3 5181297_to_5181299 Transit_Bus_Battery_Electric median 12.599589 15.491856
4 5181297_to_5181299 Transit_Bus_Battery_Electric summer 12.586869 15.491856

Analyze Energy Efficiency#

We can calculate energy efficiency in kWh per mile, including HVAC loads. The results include a scenario column (summer/winter.median) for HVAC impacts.

Weather Impacts#

Let's see how energy efficiency in kWh/mi compares across different weather scenarios:

if "scenario" in trip_results.columns:
    trip_results["kwh_per_mi"] = trip_results["energy_used"] / trip_results["miles"]

trip_results.groupby("scenario")["kwh_per_mi"].mean()
scenario
median    1.665196
summer    1.947711
winter    5.331571
Name: kwh_per_mi, dtype: float64

We can see that winter requires the greatest energy, since the cold climate in Utah requires a heavy HVAC load.

Efficiency by Route#

How does typical energy efficiency in the median scenario (meaning temperatures are taken from the day of a typical year with the median average temperature)?

We can check by filtering by scenario and then grouping by route:

# First, filter out any deadhead trips
median_results = trip_results[trip_results["trip_type"] == "service"].copy()
# Then, only include median weather impacts
median_results = median_results[median_results["scenario"] == "median"]
median_results.groupby("route_short_name")["kwh_per_mi"].mean().sort_values(
    ascending=False
)
route_short_name
807    1.295723
806    1.020666
Name: kwh_per_mi, dtype: float64

Route 807 requires more energy on average.

Access Additional Results#

Besides trip-level results, you can also access link-level results. These detailed results can help you better understand differences in predictions across trips.

# Link-level predictions show energy for each road segment
link_results = predictor.get_link_predictions()
link_results.head()
match_id edge_index edge_list_id edge_id battery_capacity trip_elevation_loss edge_grade edge_time edge_speed trip_distance ... trip_soc trip_energy_electric edge_energy_electric edge_distance trip_elevation_gain geometry shape_id vehicle energy_used energy_unit
0 0 0 0 30081 500.0 0.000000 0.034760 0.055813 27.326809 0.025420 ... 0.999840 0.080132 0.080132 0.025420 0.000884 LINESTRING (-111.71143 40.27542, -111.71156 40... -111.712,40.276->-111.895,40.426 Transit_Bus_Battery_Electric 12.359723 kWh
1 0 1 0 30082 500.0 0.000000 0.088317 0.082326 27.326809 0.055324 ... 0.999494 0.252983 0.172851 0.029904 0.003525 LINESTRING (-111.71166 40.27568, -111.71109 40... -111.712,40.276->-111.895,40.426 Transit_Bus_Battery_Electric 12.359723 kWh
2 0 2 0 246 500.0 -0.006952 -0.076052 0.359040 27.326809 0.146736 ... 0.999959 0.020278 -0.232705 0.091411 0.003525 LINESTRING (-111.71109 40.27571, -111.71139 40... -111.712,40.276->-111.895,40.426 Transit_Bus_Battery_Electric 12.359723 kWh
3 0 3 0 40037 500.0 -0.006952 0.001036 0.054291 27.326809 0.167667 ... 0.999906 0.047239 0.026962 0.020931 0.003546 LINESTRING (-111.71275 40.27596, -111.71275 40... -111.712,40.276->-111.895,40.426 Transit_Bus_Battery_Electric 12.359723 kWh
4 0 4 0 259 500.0 -0.007189 -0.013422 0.030526 34.665791 0.185304 ... 0.999897 0.051700 0.004460 0.017637 0.003546 LINESTRING (-111.71305 40.27608, -111.7131 40.... -111.712,40.276->-111.895,40.426 Transit_Bus_Battery_Electric 12.359723 kWh

5 rows × 22 columns