Skip to content

Commit d2f0401

Browse files
committed
Merge branch 'pr/209' of github.com:1kastner/conflowgen into pr/209
2 parents 2f2bb00 + ef3a103 commit d2f0401

6 files changed

+101
-49
lines changed

conflowgen/analyses/abstract_analysis.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -98,35 +98,39 @@ def _restrict_storage_requirement(selected_containers: ModelSelect, storage_requ
9898
@staticmethod
9999
def _restrict_container_delivered_by_vehicle_type(
100100
selected_containers: ModelSelect, container_delivered_by_vehicle_type: typing.Any
101-
) -> ModelSelect:
101+
) -> (ModelSelect, list[ModeOfTransport]):
102102
if hashable(container_delivered_by_vehicle_type) \
103103
and container_delivered_by_vehicle_type in set(ModeOfTransport):
104104
selected_containers = selected_containers.where(
105105
Container.delivered_by == container_delivered_by_vehicle_type
106106
)
107+
list_of_vehicle_types = [container_delivered_by_vehicle_type]
107108
else: # assume it is some kind of collection (list, set, ...)
108109
selected_containers = selected_containers.where(
109110
Container.delivered_by << container_delivered_by_vehicle_type
110111
)
111-
return selected_containers
112+
list_of_vehicle_types = list(container_delivered_by_vehicle_type)
113+
return selected_containers, list_of_vehicle_types
112114

113115
@staticmethod
114116
def _restrict_container_picked_up_by_vehicle_type(
115117
selected_containers: ModelSelect, container_picked_up_by_vehicle_type: typing.Any
116-
) -> ModelSelect:
118+
) -> (ModelSelect, list[ModeOfTransport]):
117119
if container_picked_up_by_vehicle_type == "scheduled vehicles":
118120
container_picked_up_by_vehicle_type = ModeOfTransport.get_scheduled_vehicles()
119-
121+
list_of_vehicle_types = container_picked_up_by_vehicle_type
120122
if hashable(container_picked_up_by_vehicle_type) \
121123
and container_picked_up_by_vehicle_type in set(ModeOfTransport):
122124
selected_containers = selected_containers.where(
123125
Container.picked_up_by == container_picked_up_by_vehicle_type
124126
)
127+
list_of_vehicle_types = [container_picked_up_by_vehicle_type]
125128
else: # assume it is some kind of collection (list, set, ...)
126129
selected_containers = selected_containers.where(
127130
Container.picked_up_by << container_picked_up_by_vehicle_type
128131
)
129-
return selected_containers
132+
list_of_vehicle_types = list(container_picked_up_by_vehicle_type)
133+
return selected_containers, list_of_vehicle_types
130134

131135
@staticmethod
132136
def _restrict_container_picked_up_by_initial_vehicle_type(

conflowgen/analyses/container_dwell_time_analysis.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ def get_container_dwell_times(
6464
)
6565

6666
if container_delivered_by_vehicle_type != "all":
67-
selected_containers = self._restrict_container_delivered_by_vehicle_type(
67+
selected_containers, vehicle_types = self._restrict_container_delivered_by_vehicle_type(
6868
selected_containers, container_delivered_by_vehicle_type
6969
)
7070

7171
if container_picked_up_by_vehicle_type != "all":
72-
selected_containers = self._restrict_container_picked_up_by_vehicle_type(
72+
selected_containers, vehicle_types = self._restrict_container_picked_up_by_vehicle_type(
7373
selected_containers, container_picked_up_by_vehicle_type
7474
)
7575

conflowgen/analyses/container_flow_by_vehicle_instance_analysis.py

+27-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import datetime
44
import typing
55

6+
from peewee import ModelSelect
7+
68
from conflowgen.data_summaries.data_summaries_cache import DataSummariesCache
79
from conflowgen.domain_models.container import Container
810
from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport
@@ -17,27 +19,34 @@ class ContainerFlowByVehicleInstanceAnalysis(AbstractAnalysis):
1719
as it is the case with :class:`.ContainerFlowByVehicleInstanceAnalysisReport`.
1820
"""
1921

20-
@staticmethod
2122
@DataSummariesCache.cache_result
2223
def get_container_flow_by_vehicle(
24+
self,
25+
vehicle_types: typing.Collection[ModeOfTransport] = (
26+
ModeOfTransport.train,
27+
ModeOfTransport.feeder,
28+
ModeOfTransport.deep_sea_vessel,
29+
ModeOfTransport.barge
30+
),
2331
start_date: typing.Optional[datetime.datetime] = None,
24-
end_date: typing.Optional[datetime.datetime] = None
25-
) -> typing.Dict[
32+
end_date: typing.Optional[datetime.datetime] = None,
33+
) -> [
2634
ModeOfTransport, typing.Dict[VehicleIdentifier, typing.Dict[FlowDirection, (int, int)]]
2735
]:
2836
"""
29-
This shows for each of the vehicles
30-
3137
Args:
38+
vehicle_types: A collection of vehicle types, e.g., passed as a :class:`list` or :class:`set`.
39+
Only the vehicles that correspond to the provided vehicle type(s) are considered in the analysis.
3240
start_date:
3341
The earliest arriving container that is included. Consider all containers if :obj:`None`.
3442
end_date:
3543
The latest departing container that is included. Consider all containers if :obj:`None`.
3644
3745
Returns:
38-
Grouped by vehicle type and vehicle instance, how much import, export, and transshipment is unloaded and
39-
loaded.
46+
Grouped by vehicle type and vehicle instance, how many import, export, and transshipment containers are
47+
unloaded and loaded (measured in TEU).
4048
"""
49+
4150
container_flow_by_vehicle: typing.Dict[
4251
ModeOfTransport, typing.Dict[VehicleIdentifier,
4352
typing.Dict[FlowDirection, typing.Dict[str, int]]]] = {
@@ -47,8 +56,12 @@ def get_container_flow_by_vehicle(
4756

4857
vehicle_identifier_cache = {}
4958

59+
selected_containers: ModelSelect = Container.select().where(
60+
(Container.delivered_by.in_(vehicle_types) | Container.picked_up_by.in_(vehicle_types))
61+
)
62+
5063
container: Container
51-
for container in Container.select():
64+
for container in selected_containers:
5265
if start_date and container.get_arrival_time() < start_date:
5366
continue
5467
if end_date and container.get_departure_time() > end_date:
@@ -77,7 +90,8 @@ def get_container_flow_by_vehicle(
7790
for flow_direction in FlowDirection
7891
}
7992
container_flow_by_vehicle[
80-
container.delivered_by][vehicle_id_inbound][container.flow_direction]["inbound"] += 1
93+
container.delivered_by
94+
][vehicle_id_inbound][container.flow_direction]["inbound"] += container.occupied_teu
8195

8296
if container.picked_up_by_large_scheduled_vehicle is not None: # if not transported by truck
8397

@@ -104,4 +118,8 @@ def get_container_flow_by_vehicle(
104118
container_flow_by_vehicle[
105119
container.picked_up_by][vehicle_id_outbound][container.flow_direction]["outbound"] += 1
106120

121+
for skipped_vehicle_type in set(ModeOfTransport) - set(vehicle_types):
122+
assert len(container_flow_by_vehicle[skipped_vehicle_type]) == 0
123+
del container_flow_by_vehicle[skipped_vehicle_type]
124+
107125
return container_flow_by_vehicle

conflowgen/analyses/container_flow_by_vehicle_instance_analysis_report.py

+60-30
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
import matplotlib.figure
77
import matplotlib.pyplot as plt
8+
import numpy as np
89
import pandas as pd
910

1011
from conflowgen.analyses.container_flow_by_vehicle_instance_analysis import ContainerFlowByVehicleInstanceAnalysis
11-
from conflowgen.descriptive_datatypes import VehicleIdentifier, FlowDirection
1212
from conflowgen.domain_models.data_types.mode_of_transport import ModeOfTransport
1313
from conflowgen.reporting import AbstractReportWithMatplotlib
1414
from conflowgen.reporting.no_data_plot import no_data_graph
@@ -35,10 +35,15 @@ def __init__(self):
3535
self.analysis = ContainerFlowByVehicleInstanceAnalysis()
3636

3737
def get_report_as_text(
38-
self, **kwargs
38+
self,
39+
vehicle_types: ModeOfTransport | str | typing.Collection = "scheduled vehicles",
40+
**kwargs
3941
) -> str:
4042
"""
4143
Keyword Args:
44+
vehicle_types (typing.Collection[ModeOfTransport]): A collection of vehicle types, e.g., passed as a
45+
:class:`list` or :class:`set`.
46+
Only the vehicles that correspond to the provided vehicle type(s) are considered in the analysis.
4247
start_date (datetime.datetime): The earliest arriving container that is included.
4348
Consider all containers if :obj:`None`.
4449
end_date (datetime.datetime): The latest departing container that is included.
@@ -47,38 +52,39 @@ def get_report_as_text(
4752
Returns:
4853
The report in human-readable text format
4954
"""
50-
container_flow = self._get_analysis(kwargs)
55+
plain_table, vehicle_types = self._get_analysis(kwargs)
5156

52-
report = "(pending)"
57+
if len(plain_table) > 0:
58+
df = self._get_dataframe_from_plain_table(plain_table)
59+
report = str(df)
60+
else:
61+
report = "(no report feasible because no data is available)"
5362

54-
# create string representation
5563
return report
5664

57-
def _get_analysis(self, kwargs: dict) -> typing.Dict[
58-
ModeOfTransport, typing.Dict[VehicleIdentifier, typing.Dict[FlowDirection, (int, int)]]
59-
]:
65+
def _get_analysis(self, kwargs: dict) -> (list, list):
6066
start_date = kwargs.pop("start_date", None)
6167
end_date = kwargs.pop("end_date", None)
68+
vehicle_types = kwargs.pop("vehicle_types", (
69+
ModeOfTransport.train,
70+
ModeOfTransport.feeder,
71+
ModeOfTransport.deep_sea_vessel,
72+
ModeOfTransport.barge
73+
))
74+
6275
assert len(kwargs) == 0, f"Keyword(s) {kwargs.keys()} have not been processed"
6376

6477
container_flow = self.analysis.get_container_flow_by_vehicle(
78+
vehicle_types=vehicle_types,
6579
start_date=start_date,
66-
end_date=end_date
80+
end_date=end_date,
6781
)
68-
return container_flow
69-
70-
def get_report_as_graph(self, **kwargs) -> matplotlib.figure.Figure:
71-
"""
72-
Visualize the container flows (import, export, transshipment) over time.
7382

74-
Returns:
75-
The diagram.
76-
"""
77-
container_flow = self._get_analysis(kwargs)
83+
vehicle_types = list(container_flow.keys())
7884

7985
plain_table = []
8086

81-
for mode_of_transport in (set(container_flow.keys()) - set([ModeOfTransport.truck])):
87+
for mode_of_transport in vehicle_types:
8288
for vehicle_identifier in container_flow[mode_of_transport].keys():
8389
for flow_direction in container_flow[mode_of_transport][vehicle_identifier]:
8490
for journey_direction in container_flow[mode_of_transport][vehicle_identifier][flow_direction]:
@@ -91,35 +97,59 @@ def get_report_as_graph(self, **kwargs) -> matplotlib.figure.Figure:
9197
str(flow_direction), journey_direction, handled_volume)
9298
)
9399

100+
return plain_table, vehicle_types
101+
102+
def get_report_as_graph(self, **kwargs) -> matplotlib.figure.Figure:
103+
"""
104+
Keyword Args:
105+
vehicle_types (typing.Collection[ModeOfTransport]): A collection of vehicle types, e.g., passed as a
106+
:class:`list` or :class:`set`.
107+
Only the vehicles that correspond to the provided vehicle type(s) are considered in the analysis.
108+
start_date (datetime.datetime):
109+
The earliest arriving container that is included. Consider all containers if :obj:`None`.
110+
end_date (datetime.datetime):
111+
The latest departing container that is included. Consider all containers if :obj:`None`.
112+
113+
Returns:
114+
Grouped by vehicle type and vehicle instance, how many import, export, and transshipment containers are
115+
unloaded and loaded (measured in TEU).
116+
"""
117+
plain_table, vehicle_types = self._get_analysis(kwargs)
118+
94119
plot_title = "Container Flow By Vehicle Instance Analysis Report"
95120

96121
if len(plain_table) == 0:
97122
fig, ax = no_data_graph()
98123
ax.set_title(plot_title)
99124
return fig
100125

101-
column_names = ("mode_of_transport", "vehicle_id", "vehicle_name", "service_name", "vehicle_arrival_time",
102-
"flow_direction", "journey_direction", "handled_volume")
103-
104-
df = pd.DataFrame(plain_table)
105-
df.columns = column_names
106-
df.set_index("vehicle_arrival_time", inplace=True)
107-
108-
self._df = df
126+
df = self._get_dataframe_from_plain_table(plain_table)
109127

110-
fig, axes = plt.subplots(nrows=2 * (len(ModeOfTransport) - 1), figsize=(7, 20))
128+
number_subplots = 2 * len(vehicle_types)
129+
fig, axes = plt.subplots(nrows=number_subplots, figsize=(7, 4 * number_subplots))
111130
i = 0
112-
for mode_of_transport in (set(ModeOfTransport) - set([ModeOfTransport.truck])):
131+
for mode_of_transport in vehicle_types:
113132
for journey_direction in ["inbound", "outbound"]:
114133
ax = axes[i]
115134
i += 1
116-
ax.set_title(f"{mode_of_transport} - {journey_direction}")
135+
ax.set_title(f"{str(mode_of_transport).replace('_', ' ').capitalize()} - {journey_direction}")
117136
df[(df["mode_of_transport"] == mode_of_transport)
118137
& (df["journey_direction"] == journey_direction)
119138
].groupby("flow_direction")["handled_volume"].plot(ax=ax, linestyle=":", marker=".")
139+
ax.set_ylabel("")
120140

121141
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
122142

123143
plt.tight_layout()
124144

125145
return fig
146+
147+
def _get_dataframe_from_plain_table(self, plain_table):
148+
column_names = ("mode_of_transport", "vehicle_id", "vehicle_name", "service_name", "vehicle_arrival_time",
149+
"flow_direction", "journey_direction", "handled_volume")
150+
df = pd.DataFrame(plain_table)
151+
df.columns = column_names
152+
df.set_index("vehicle_arrival_time", inplace=True)
153+
df.replace(0, np.nan, inplace=True)
154+
self._df = df
155+
return df

conflowgen/analyses/container_flow_vehicle_type_adjustment_per_vehicle_analysis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def get_vehicle_type_adjustments_per_vehicle(
6161
)
6262

6363
if adjusted_vehicle_type is not None and adjusted_vehicle_type != "all":
64-
selected_containers = self._restrict_container_picked_up_by_vehicle_type(
64+
selected_containers, list_of_vehicle_types = self._restrict_container_picked_up_by_vehicle_type(
6565
selected_containers, adjusted_vehicle_type
6666
)
6767

conflowgen/analyses/modal_split_analysis_report.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ class ModalSplitAnalysisReport(AbstractReportWithMatplotlib):
1818
"""
1919

2020
report_description = """
21-
Analyze the amount of containers dedicated for or coming from the hinterland compared to the amount of containers
22-
that are transshipment.
21+
Analyze how many containers are delivered and picked up by which type of vehicle.
22+
This counts the containers in the yard, i.e., transshipment containers are *not* counted twice.
2323
"""
2424

2525
def __init__(self):

0 commit comments

Comments
 (0)