eflips-eval 1.2.2__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eflips-eval
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary:
5
5
  Author: Ludger Heide
6
6
  Author-email: ludger.heide@lhtechnologies.de
@@ -10,9 +10,12 @@ Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Requires-Dist: dash-cytoscape (>=1.0.1,<2.0.0)
13
- Requires-Dist: eflips-model (>=2.3.0,<4.0.0)
13
+ Requires-Dist: eflips-model (>=2.3.0,<5.0.0)
14
+ Requires-Dist: folium (>=0.17.0,<0.18.0)
14
15
  Requires-Dist: pandas (>=2.1.4,<3.0.0)
15
16
  Requires-Dist: plotly (>=5.19.0,<6.0.0)
17
+ Requires-Dist: seaborn (>=0.13.2,<0.14.0)
18
+ Requires-Dist: shapely (>=2.0.5,<3.0.0)
16
19
  Description-Content-Type: text/markdown
17
20
 
18
21
  [![Unit Tests](https://github.com/mpm-tu-berlin/eflips-eval/actions/workflows/unittests.yml/badge.svg)](https://github.com/mpm-tu-berlin/eflips-eval/actions/workflows/unittests.yml)
@@ -5,6 +5,7 @@ from typing import Dict, List
5
5
  import pandas as pd
6
6
  import sqlalchemy
7
7
  from eflips.model import Rotation, Trip, Route
8
+ from shapely import wkb # type: ignore
8
9
 
9
10
 
10
11
  def rotation_info(
@@ -98,6 +99,98 @@ def rotation_info(
98
99
  return df
99
100
 
100
101
 
102
+ def geographic_trip_plot(
103
+ scenario_id: int,
104
+ session: sqlalchemy.orm.session.Session,
105
+ rotation_ids: None | int | List[int] = None,
106
+ ) -> pd.DataFrame:
107
+ """
108
+ This function creates a dataframe that can be used to visualize the geographic distribution of rotations. It creates
109
+ a dataframe with one row for each trip and the following columns:
110
+
111
+ - rotation_id: the id of the rotation
112
+ - rotation_name: the name of the rotation
113
+ - vehicle_type_id: the id of the vehicle type
114
+ - vehicle_type_name: the name of the vehicle type
115
+ - originating_depot_id: the id of the originating depot
116
+ - originating_depot_name: the name of the originating depot
117
+ - distance: the distance of the route
118
+ - coordinates: An array of *(lon, lat)* tuples with the coordinates of the route - the shape if set, otherwise the stops
119
+ - line_name: the name of the line, which is the first part of the rotation name. Used for sorting
120
+
121
+ :param scenario_id:
122
+ :param session:
123
+ :param rotation_ids:
124
+ :return: a pandas DataFrame
125
+ """
126
+ rotations_q = session.query(Rotation).filter(Rotation.scenario_id == scenario_id)
127
+ if rotation_ids is not None:
128
+ if isinstance(rotation_ids, int):
129
+ rotation_ids = [rotation_ids]
130
+ rotations_q = rotations_q.filter(Rotation.id.in_(rotation_ids))
131
+ rotations_q = rotations_q.options(
132
+ sqlalchemy.orm.joinedload(Rotation.trips)
133
+ .joinedload(Trip.route)
134
+ .joinedload(Route.line)
135
+ )
136
+ rotations_q = rotations_q.options(
137
+ sqlalchemy.orm.joinedload(Rotation.vehicle_type),
138
+ )
139
+ rotations_q = rotations_q.options(
140
+ sqlalchemy.orm.joinedload(Rotation.trips)
141
+ .joinedload(Trip.route)
142
+ .joinedload(Route.departure_station),
143
+ )
144
+
145
+ result: List[Dict[str, int | float | str | datetime]] = []
146
+ for rotation in rotations_q:
147
+ origin_depot_id = rotation.trips[0].route.departure_station_id
148
+ origin_depot_name = rotation.trips[0].route.departure_station.name
149
+ line_name = rotation.trips[0].route.line.name
150
+ vehicle_type_id = rotation.vehicle_type_id
151
+ vehicle_type_name = rotation.vehicle_type.name
152
+
153
+ for trip in rotation.trips:
154
+ # Obtain the coordinates of the route
155
+ if trip.route.geom is not None:
156
+ raise NotImplementedError(
157
+ "Geometries are not yet supported. Check if the code below 'just works'."
158
+ "If not, you need to implement the conversion to coordinates."
159
+ )
160
+ line_geom = wkb.loads(bytes(trip.route.geom.data))
161
+ line_coords = [(point.y, point.x) for point in line_geom.coords]
162
+ else:
163
+ line_coords = []
164
+ point_geom = wkb.loads(bytes(trip.route.departure_station.geom.data))
165
+ lon, lat = point_geom.x, point_geom.y
166
+ line_coords.append((lat, lon))
167
+ for assoc in trip.route.assoc_route_stations:
168
+ if assoc.location is not None:
169
+ station_coordinates = wkb.loads(bytes(assoc.location.data))
170
+ else:
171
+ station_coordinates = wkb.loads(bytes(assoc.station.geom.data))
172
+ lon, lat = station_coordinates.x, station_coordinates.y
173
+ line_coords.append((lat, lon))
174
+ point_geom = wkb.loads(bytes(trip.route.arrival_station.geom.data))
175
+ lon, lat = point_geom.x, point_geom.y
176
+ line_coords.append((lat, lon))
177
+
178
+ result.append(
179
+ {
180
+ "rotation_id": rotation.id,
181
+ "rotation_name": rotation.name,
182
+ "vehicle_type_id": vehicle_type_id,
183
+ "vehicle_type_name": vehicle_type_name,
184
+ "originating_depot_id": origin_depot_id,
185
+ "originating_depot_name": origin_depot_name,
186
+ "distance": trip.route.distance,
187
+ "coordinates": line_coords,
188
+ "line_name": line_name,
189
+ }
190
+ )
191
+ return pd.DataFrame(result)
192
+
193
+
101
194
  def single_rotation_info(
102
195
  rotation_id: int,
103
196
  session: sqlalchemy.orm.session.Session,
@@ -1,7 +1,9 @@
1
1
  import dash_cytoscape as cyto # type: ignore
2
+ import folium # type: ignore
2
3
  import pandas as pd
3
4
  import plotly.express as px # type: ignore
4
5
  import plotly.graph_objs as go # type: ignore
6
+ import seaborn as sns # type: ignore
5
7
  from eflips.model import TripType
6
8
 
7
9
 
@@ -55,6 +57,58 @@ def rotation_info(prepared_data: pd.DataFrame) -> go.Figure:
55
57
  return fig
56
58
 
57
59
 
60
+ def geographic_trip_plot(prepared_data: pd.DataFrame) -> folium.Map:
61
+ """
62
+ This function visualizes the trips on a map using folium. The trips are lines between the departure and arrival
63
+ stations.
64
+
65
+
66
+
67
+ :param prepared_data: A Pandas dataframe with the following columns:
68
+ - rotation_id: the id of the rotation
69
+ - rotation_name: the name of the rotation
70
+ - vehicle_type_id: the id of the vehicle type
71
+ - vehicle_type_name: the name of the vehicle type
72
+ - originating_depot_id: the id of the originating depot
73
+ - originating_depot_name: the name of the originating depot
74
+ - distance: the distance of the route
75
+ - coordinates: An array of *(lon, lat)* tuples with the coordinates of the route - the shape if set, otherwise the stops
76
+ - line_name: the name of the line, which is the first part of the rotation name. Used for sorting
77
+ :return: A folium map object
78
+ """
79
+ # Use seaborn to create a color palette for each originating depot
80
+ palette = sns.color_palette(
81
+ "husl", n_colors=len(prepared_data["originating_depot_id"].unique())
82
+ )
83
+ colors = {}
84
+ for depot_name in prepared_data["originating_depot_name"].unique():
85
+ color = palette.pop(0)
86
+ # Turn the color into a hex string
87
+ color = "#{:02x}{:02x}{:02x}".format(*[int(c * 255) for c in color])
88
+ colors[depot_name] = color
89
+
90
+ # Obtain the mean latitude and longitude for the map center
91
+ lat_center = (
92
+ prepared_data["coordinates"]
93
+ .apply(lambda x: sum([c[0] for c in x]) / len(x))
94
+ .mean()
95
+ )
96
+ lon_center = (
97
+ prepared_data["coordinates"]
98
+ .apply(lambda x: sum([c[1] for c in x]) / len(x))
99
+ .mean()
100
+ )
101
+
102
+ map = folium.Map(
103
+ location=[lat_center, lon_center], zoom_start=11, tiles="Cartodb dark_matter"
104
+ )
105
+ for i, row in prepared_data.iterrows():
106
+ color = colors[row["originating_depot_name"]]
107
+ pl = folium.PolyLine(row["coordinates"], color=color, weight=2.5, opacity=1)
108
+ map.add_child(pl)
109
+ return map
110
+
111
+
58
112
  def single_rotation_info(prepared_data: pd.DataFrame) -> cyto.Cytoscape:
59
113
  """
60
114
  This plots a single rotation as a network graph. The nodes are the stops and the edges are the trips between the
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "eflips-eval"
3
- version = "1.2.2"
3
+ version = "1.3.0"
4
4
  description = ""
5
5
  authors = [
6
6
  "Ludger Heide <ludger.heide@lhtechnologies.de>",
@@ -13,10 +13,13 @@ include= ["LICENSE.md"]
13
13
 
14
14
  [tool.poetry.dependencies]
15
15
  python = "^3.10"
16
- eflips-model = ">=2.3.0,<4.0.0"
16
+ eflips-model = ">=2.3.0,<5.0.0"
17
17
  pandas = "^2.1.4"
18
18
  plotly = "^5.19.0"
19
19
  dash-cytoscape = "^1.0.1"
20
+ shapely = "^2.0.5"
21
+ folium = "^0.17.0"
22
+ seaborn = "^0.13.2"
20
23
 
21
24
  [tool.poetry.group.dev.dependencies]
22
25
  mypy = "^1.7.1"
File without changes
File without changes