tokev commited on
Commit
248a619
·
verified ·
1 Parent(s): 5893134

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. data/generated/city_0002/scenarios/accident/config.json +13 -13
  2. data/generated/city_0002/scenarios/construction/config.json +13 -13
  3. data/generated/city_0002/scenarios/district_overload/config.json +13 -13
  4. data/generated/city_0002/scenarios/evening_rush/config.json +13 -13
  5. data/generated/city_0002/scenarios/event_spike/config.json +13 -13
  6. data/generated/city_0002/scenarios/morning_rush/config.json +13 -13
  7. data/generated/city_0002/scenarios/normal/config.json +13 -13
  8. data/splits/splits/README.md +21 -0
  9. data/splits/splits/test_cities.txt +15 -0
  10. data/splits/splits/train_cities.txt +70 -0
  11. data/splits/splits/val_cities.txt +15 -0
  12. server/environment.py +168 -168
  13. server/remote_runner.py +58 -58
  14. server/roadnet_matcher.py +55 -55
  15. server/visualizer_app.py +404 -404
  16. third_party/CityFlow/.gitignore +13 -13
  17. third_party/CityFlow/.gitmodules +6 -6
  18. third_party/CityFlow/CMakeLists.txt +76 -76
  19. third_party/CityFlow/Dockerfile +20 -20
  20. third_party/CityFlow/LICENSE.txt +200 -200
  21. third_party/CityFlow/README.rst +51 -51
  22. third_party/CityFlow/docs/Makefile +19 -19
  23. third_party/CityFlow/docs/make.bat +36 -36
  24. third_party/CityFlow/docs/source/conf.py +154 -154
  25. third_party/CityFlow/docs/source/flow.rst +23 -23
  26. third_party/CityFlow/docs/source/index.rst +18 -18
  27. third_party/CityFlow/docs/source/install.rst +66 -66
  28. third_party/CityFlow/examples/config.json +11 -11
  29. third_party/CityFlow/examples/flow.json +241 -241
  30. third_party/CityFlow/frontend/Point.js +56 -56
  31. third_party/CityFlow/frontend/README.md +6 -6
  32. third_party/CityFlow/frontend/download_replay.py +18 -18
  33. third_party/CityFlow/frontend/index.html +136 -136
  34. third_party/CityFlow/frontend/script.js +1035 -1035
  35. third_party/CityFlow/frontend/script_multi.js +0 -0
  36. third_party/CityFlow/frontend/spinner.css +52 -52
  37. third_party/CityFlow/frontend/style.css +63 -63
  38. third_party/CityFlow/frontend/style_multi.css +747 -747
  39. third_party/CityFlow/setup.py +70 -70
  40. third_party/CityFlow/src/CMakeLists.txt +35 -35
  41. third_party/CityFlow/src/cityflow.cpp +47 -47
  42. third_party/CityFlow/tests/CMakeLists.txt +13 -13
  43. third_party/CityFlow/tools/debug/CMakeLists.txt +8 -8
  44. third_party/CityFlow/tools/debug/simple_run.cpp +58 -58
  45. third_party/CityFlow/tools/generator/generate_grid_scenario.py +127 -127
  46. third_party/CityFlow/tools/generator/generate_json_from_grid.py +428 -428
  47. third_party/CityFlow/tools/generator/readme.md +41 -41
  48. training/README.md +38 -38
  49. training/__init__.py +6 -6
  50. training/cityflow_dataset.py +202 -202
data/generated/city_0002/scenarios/accident/config.json CHANGED
@@ -1,13 +1,13 @@
1
- {
2
- "interval": 1.0,
3
- "seed": 11348,
4
- "dir": "/root/aditya/agentic-traffic/data/generated/city_0002/",
5
- "roadnetFile": "roadnet.json",
6
- "flowFile": "scenarios/accident/flow.json",
7
- "rlTrafficLight": true,
8
- "laneChange": false,
9
- "saveReplay": false,
10
- "roadnetLogFile": "scenarios/accident/roadnetLogFile.json",
11
- "replayLogFile": "scenarios/accident/replay.txt",
12
- "step": 3600
13
- }
 
1
+ {
2
+ "interval": 1.0,
3
+ "seed": 11348,
4
+ "dir": "C:/Users/toke/Desktop/agentic-traffic/data/generated/city_0002/",
5
+ "roadnetFile": "roadnet.json",
6
+ "flowFile": "scenarios/accident/flow.json",
7
+ "rlTrafficLight": true,
8
+ "laneChange": false,
9
+ "saveReplay": false,
10
+ "roadnetLogFile": "scenarios/accident/roadnetLogFile.json",
11
+ "replayLogFile": "scenarios/accident/replay.txt",
12
+ "step": 3600
13
+ }
data/generated/city_0002/scenarios/construction/config.json CHANGED
@@ -1,13 +1,13 @@
1
- {
2
- "interval": 1.0,
3
- "seed": 11449,
4
- "dir": "/root/aditya/agentic-traffic/data/generated/city_0002/",
5
- "roadnetFile": "roadnet.json",
6
- "flowFile": "scenarios/construction/flow.json",
7
- "rlTrafficLight": true,
8
- "laneChange": false,
9
- "saveReplay": false,
10
- "roadnetLogFile": "scenarios/construction/roadnetLogFile.json",
11
- "replayLogFile": "scenarios/construction/replay.txt",
12
- "step": 3600
13
- }
 
1
+ {
2
+ "interval": 1.0,
3
+ "seed": 11449,
4
+ "dir": "C:/Users/toke/Desktop/agentic-traffic/data/generated/city_0002/",
5
+ "roadnetFile": "roadnet.json",
6
+ "flowFile": "scenarios/construction/flow.json",
7
+ "rlTrafficLight": true,
8
+ "laneChange": false,
9
+ "saveReplay": false,
10
+ "roadnetLogFile": "scenarios/construction/roadnetLogFile.json",
11
+ "replayLogFile": "scenarios/construction/replay.txt",
12
+ "step": 3600
13
+ }
data/generated/city_0002/scenarios/district_overload/config.json CHANGED
@@ -1,13 +1,13 @@
1
- {
2
- "interval": 1.0,
3
- "seed": 11651,
4
- "dir": "/root/aditya/agentic-traffic/data/generated/city_0002/",
5
- "roadnetFile": "roadnet.json",
6
- "flowFile": "scenarios/district_overload/flow.json",
7
- "rlTrafficLight": true,
8
- "laneChange": false,
9
- "saveReplay": false,
10
- "roadnetLogFile": "scenarios/district_overload/roadnetLogFile.json",
11
- "replayLogFile": "scenarios/district_overload/replay.txt",
12
- "step": 3600
13
- }
 
1
+ {
2
+ "interval": 1.0,
3
+ "seed": 11651,
4
+ "dir": "C:/Users/toke/Desktop/agentic-traffic/data/generated/city_0002/",
5
+ "roadnetFile": "roadnet.json",
6
+ "flowFile": "scenarios/district_overload/flow.json",
7
+ "rlTrafficLight": true,
8
+ "laneChange": false,
9
+ "saveReplay": false,
10
+ "roadnetLogFile": "scenarios/district_overload/roadnetLogFile.json",
11
+ "replayLogFile": "scenarios/district_overload/replay.txt",
12
+ "step": 3600
13
+ }
data/generated/city_0002/scenarios/evening_rush/config.json CHANGED
@@ -1,13 +1,13 @@
1
- {
2
- "interval": 1.0,
3
- "seed": 11247,
4
- "dir": "/root/aditya/agentic-traffic/data/generated/city_0002/",
5
- "roadnetFile": "roadnet.json",
6
- "flowFile": "scenarios/evening_rush/flow.json",
7
- "rlTrafficLight": true,
8
- "laneChange": false,
9
- "saveReplay": false,
10
- "roadnetLogFile": "scenarios/evening_rush/roadnetLogFile.json",
11
- "replayLogFile": "scenarios/evening_rush/replay.txt",
12
- "step": 3600
13
- }
 
1
+ {
2
+ "interval": 1.0,
3
+ "seed": 11247,
4
+ "dir": "C:/Users/toke/Desktop/agentic-traffic/data/generated/city_0002/",
5
+ "roadnetFile": "roadnet.json",
6
+ "flowFile": "scenarios/evening_rush/flow.json",
7
+ "rlTrafficLight": true,
8
+ "laneChange": false,
9
+ "saveReplay": false,
10
+ "roadnetLogFile": "scenarios/evening_rush/roadnetLogFile.json",
11
+ "replayLogFile": "scenarios/evening_rush/replay.txt",
12
+ "step": 3600
13
+ }
data/generated/city_0002/scenarios/event_spike/config.json CHANGED
@@ -1,13 +1,13 @@
1
- {
2
- "interval": 1.0,
3
- "seed": 11550,
4
- "dir": "/root/aditya/agentic-traffic/data/generated/city_0002/",
5
- "roadnetFile": "roadnet.json",
6
- "flowFile": "scenarios/event_spike/flow.json",
7
- "rlTrafficLight": true,
8
- "laneChange": false,
9
- "saveReplay": false,
10
- "roadnetLogFile": "scenarios/event_spike/roadnetLogFile.json",
11
- "replayLogFile": "scenarios/event_spike/replay.txt",
12
- "step": 3600
13
- }
 
1
+ {
2
+ "interval": 1.0,
3
+ "seed": 11550,
4
+ "dir": "C:/Users/toke/Desktop/agentic-traffic/data/generated/city_0002/",
5
+ "roadnetFile": "roadnet.json",
6
+ "flowFile": "scenarios/event_spike/flow.json",
7
+ "rlTrafficLight": true,
8
+ "laneChange": false,
9
+ "saveReplay": false,
10
+ "roadnetLogFile": "scenarios/event_spike/roadnetLogFile.json",
11
+ "replayLogFile": "scenarios/event_spike/replay.txt",
12
+ "step": 3600
13
+ }
data/generated/city_0002/scenarios/morning_rush/config.json CHANGED
@@ -1,13 +1,13 @@
1
- {
2
- "interval": 1.0,
3
- "seed": 11146,
4
- "dir": "/root/aditya/agentic-traffic/data/generated/city_0002/",
5
- "roadnetFile": "roadnet.json",
6
- "flowFile": "scenarios/morning_rush/flow.json",
7
- "rlTrafficLight": true,
8
- "laneChange": false,
9
- "saveReplay": false,
10
- "roadnetLogFile": "scenarios/morning_rush/roadnetLogFile.json",
11
- "replayLogFile": "scenarios/morning_rush/replay.txt",
12
- "step": 3600
13
- }
 
1
+ {
2
+ "interval": 1.0,
3
+ "seed": 11146,
4
+ "dir": "C:/Users/toke/Desktop/agentic-traffic/data/generated/city_0002/",
5
+ "roadnetFile": "roadnet.json",
6
+ "flowFile": "scenarios/morning_rush/flow.json",
7
+ "rlTrafficLight": true,
8
+ "laneChange": false,
9
+ "saveReplay": false,
10
+ "roadnetLogFile": "scenarios/morning_rush/roadnetLogFile.json",
11
+ "replayLogFile": "scenarios/morning_rush/replay.txt",
12
+ "step": 3600
13
+ }
data/generated/city_0002/scenarios/normal/config.json CHANGED
@@ -1,13 +1,13 @@
1
- {
2
- "interval": 1.0,
3
- "seed": 11045,
4
- "dir": "/root/aditya/agentic-traffic/data/generated/city_0002/",
5
- "roadnetFile": "roadnet.json",
6
- "flowFile": "scenarios/normal/flow.json",
7
- "rlTrafficLight": true,
8
- "laneChange": false,
9
- "saveReplay": false,
10
- "roadnetLogFile": "scenarios/normal/roadnetLogFile.json",
11
- "replayLogFile": "scenarios/normal/replay.txt",
12
- "step": 3600
13
- }
 
1
+ {
2
+ "interval": 1.0,
3
+ "seed": 11045,
4
+ "dir": "C:/Users/toke/Desktop/agentic-traffic/data/generated/city_0002/",
5
+ "roadnetFile": "roadnet.json",
6
+ "flowFile": "scenarios/normal/flow.json",
7
+ "rlTrafficLight": true,
8
+ "laneChange": false,
9
+ "saveReplay": false,
10
+ "roadnetLogFile": "scenarios/normal/roadnetLogFile.json",
11
+ "replayLogFile": "scenarios/normal/replay.txt",
12
+ "step": 3600
13
+ }
data/splits/splits/README.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # data/splits
2
+
3
+ City-level train/validation/test splits for the generated dataset.
4
+
5
+ ## Files
6
+
7
+ - [train_cities.txt](/Users/aditya/Developer/traffic-llm/data/splits/train_cities.txt)
8
+ - [val_cities.txt](/Users/aditya/Developer/traffic-llm/data/splits/val_cities.txt)
9
+ - [test_cities.txt](/Users/aditya/Developer/traffic-llm/data/splits/test_cities.txt)
10
+
11
+ ## Important rule
12
+
13
+ Splits are by city only. All scenarios for a given city belong to the same split.
14
+
15
+ ## Regeneration
16
+
17
+ Use:
18
+
19
+ `python3 -m training.train_local_policy make-splits`
20
+
21
+ The split logic is implemented in [training/dataset.py](/Users/aditya/Developer/traffic-llm/training/dataset.py).
data/splits/splits/test_cities.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ city_0005
2
+ city_0007
3
+ city_0008
4
+ city_0010
5
+ city_0012
6
+ city_0013
7
+ city_0020
8
+ city_0028
9
+ city_0042
10
+ city_0047
11
+ city_0051
12
+ city_0065
13
+ city_0069
14
+ city_0075
15
+ city_0084
data/splits/splits/train_cities.txt ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ city_0001
2
+ city_0002
3
+ city_0003
4
+ city_0004
5
+ city_0006
6
+ city_0011
7
+ city_0014
8
+ city_0015
9
+ city_0017
10
+ city_0018
11
+ city_0019
12
+ city_0021
13
+ city_0022
14
+ city_0023
15
+ city_0024
16
+ city_0025
17
+ city_0026
18
+ city_0027
19
+ city_0030
20
+ city_0032
21
+ city_0033
22
+ city_0034
23
+ city_0035
24
+ city_0036
25
+ city_0037
26
+ city_0038
27
+ city_0039
28
+ city_0040
29
+ city_0041
30
+ city_0043
31
+ city_0044
32
+ city_0045
33
+ city_0046
34
+ city_0048
35
+ city_0049
36
+ city_0050
37
+ city_0052
38
+ city_0053
39
+ city_0057
40
+ city_0058
41
+ city_0059
42
+ city_0060
43
+ city_0061
44
+ city_0062
45
+ city_0063
46
+ city_0064
47
+ city_0066
48
+ city_0067
49
+ city_0068
50
+ city_0070
51
+ city_0072
52
+ city_0074
53
+ city_0076
54
+ city_0077
55
+ city_0079
56
+ city_0080
57
+ city_0081
58
+ city_0082
59
+ city_0083
60
+ city_0085
61
+ city_0087
62
+ city_0088
63
+ city_0089
64
+ city_0092
65
+ city_0093
66
+ city_0094
67
+ city_0095
68
+ city_0097
69
+ city_0099
70
+ city_0100
data/splits/splits/val_cities.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ city_0009
2
+ city_0016
3
+ city_0029
4
+ city_0031
5
+ city_0054
6
+ city_0055
7
+ city_0056
8
+ city_0071
9
+ city_0073
10
+ city_0078
11
+ city_0086
12
+ city_0090
13
+ city_0091
14
+ city_0096
15
+ city_0098
server/environment.py CHANGED
@@ -1,168 +1,168 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- import uuid
5
- from pathlib import Path
6
- from typing import Any
7
-
8
- from district_llm.inference import DistrictLLMInference
9
- from district_llm.schema import DistrictAction
10
- from models import (
11
- AgenticTrafficAction,
12
- AgenticTrafficObservation,
13
- AgenticTrafficState,
14
- )
15
- from openenv.core.env_server.interfaces import Environment
16
- from openenv_app.openenv_wrapper import OpenEnvTrafficWrapper
17
-
18
-
19
- REPO_ROOT = Path(__file__).resolve().parents[1]
20
- DATA_DIR = Path(os.environ.get("DATA_DIR", "") or (REPO_ROOT / "data" / "generated"))
21
- SPLITS_DIR = Path(os.environ.get("SPLITS_DIR", "") or (REPO_ROOT / "data" / "splits"))
22
- DISTRICT_LLM_ADAPTER_PATH = Path(
23
- os.environ.get("DISTRICT_LLM_ADAPTER_PATH", "")
24
- or (REPO_ROOT / "artifacts" / "district_llm_adapter_v3" / "main_run" / "adapter")
25
- )
26
- DISTRICT_LLM_DEVICE = os.environ.get("DISTRICT_LLM_DEVICE")
27
-
28
-
29
- class AgenticTrafficEnvironment(
30
- Environment[AgenticTrafficAction, AgenticTrafficObservation, AgenticTrafficState]
31
- ):
32
- """Minimal OpenEnv-compatible wrapper around the existing district controller stack."""
33
-
34
- def __init__(self) -> None:
35
- super().__init__()
36
- self.wrapper = OpenEnvTrafficWrapper(
37
- generated_root=DATA_DIR,
38
- splits_root=SPLITS_DIR,
39
- )
40
- self._state = AgenticTrafficState()
41
- self._llm_inference: DistrictLLMInference | None = None
42
- self._llm_load_attempted = False
43
- self._llm_error: str | None = None
44
-
45
- def reset(
46
- self,
47
- seed: int | None = None,
48
- episode_id: str | None = None,
49
- **kwargs: Any,
50
- ) -> AgenticTrafficObservation:
51
- payload = self.wrapper.reset(
52
- seed=seed,
53
- city_id=kwargs.get("city_id"),
54
- scenario_name=kwargs.get("scenario_name"),
55
- )
56
- self._state.episode_id = episode_id or str(uuid.uuid4())
57
- self._state.step_count = 0
58
- self._sync_state()
59
- observation = AgenticTrafficObservation.model_validate(payload["observation"])
60
- observation.reward = None
61
- observation.done = False
62
- observation.metadata["llm"] = self._llm_status()
63
- return observation
64
-
65
- def step(
66
- self,
67
- action: AgenticTrafficAction,
68
- timeout_s: float | None = None,
69
- **kwargs: Any,
70
- ) -> AgenticTrafficObservation:
71
- del timeout_s, kwargs
72
- payload = self.wrapper.step(action=self._build_step_payload(action))
73
- self._state.step_count += 1
74
- self._sync_state()
75
- observation = AgenticTrafficObservation.model_validate(payload["observation"])
76
- observation.done = bool(payload.get("done", False))
77
- observation.reward = float(payload.get("reward", 0.0))
78
- observation.metadata["llm"] = self._llm_status()
79
- return observation
80
-
81
- @property
82
- def state(self) -> AgenticTrafficState:
83
- self._sync_state()
84
- return self._state
85
-
86
- def _build_step_payload(self, action: AgenticTrafficAction) -> dict[str, Any]:
87
- district_actions = dict(action.district_actions)
88
- llm_generated_actions: dict[str, Any] = {}
89
-
90
- if action.use_llm:
91
- llm_generated_actions = self._generate_llm_actions(
92
- existing_actions=district_actions,
93
- max_new_tokens=action.llm_max_new_tokens,
94
- )
95
- for district_id, directive in llm_generated_actions.items():
96
- district_actions.setdefault(district_id, directive)
97
-
98
- payload = {"district_actions": district_actions}
99
- payload["metadata"] = {
100
- "use_llm": bool(action.use_llm),
101
- "llm_generated_districts": sorted(llm_generated_actions),
102
- "llm": self._llm_status(),
103
- }
104
- return payload
105
-
106
- def _generate_llm_actions(
107
- self,
108
- existing_actions: dict[str, Any],
109
- max_new_tokens: int,
110
- ) -> dict[str, Any]:
111
- if not self.wrapper.last_summaries:
112
- return {}
113
-
114
- inference = self._get_llm_inference()
115
- if inference is None:
116
- return {}
117
-
118
- generated_actions: dict[str, Any] = {}
119
- for district_id, summary in self.wrapper.last_summaries.items():
120
- if district_id in existing_actions:
121
- continue
122
- result = inference.predict_with_result(summary=summary, max_new_tokens=max_new_tokens)
123
- generated_actions[district_id] = result.action.to_dict()
124
- return generated_actions
125
-
126
- def _get_llm_inference(self) -> DistrictLLMInference | None:
127
- if self._llm_inference is not None:
128
- return self._llm_inference
129
- if self._llm_load_attempted:
130
- return None
131
-
132
- self._llm_load_attempted = True
133
- if not DISTRICT_LLM_ADAPTER_PATH.exists():
134
- self._llm_error = f"Adapter not found at {DISTRICT_LLM_ADAPTER_PATH}"
135
- return None
136
-
137
- try:
138
- self._llm_inference = DistrictLLMInference(
139
- model_name_or_path=str(DISTRICT_LLM_ADAPTER_PATH),
140
- device=DISTRICT_LLM_DEVICE,
141
- fallback_action=DistrictAction.default_hold(
142
- duration_steps=self.wrapper.district_decision_interval
143
- ),
144
- )
145
- except Exception as exc:
146
- self._llm_error = f"{type(exc).__name__}: {exc}"
147
- self._llm_inference = None
148
- return self._llm_inference
149
-
150
- def _llm_status(self) -> dict[str, Any]:
151
- return {
152
- "adapter_path": str(DISTRICT_LLM_ADAPTER_PATH),
153
- "adapter_present": DISTRICT_LLM_ADAPTER_PATH.exists(),
154
- "loaded": self._llm_inference is not None,
155
- "load_attempted": self._llm_load_attempted,
156
- "error": self._llm_error,
157
- }
158
-
159
- def _sync_state(self) -> None:
160
- payload = self.wrapper.state()["state"]
161
- self._state = AgenticTrafficState.model_validate(
162
- {
163
- **payload,
164
- "episode_id": self._state.episode_id,
165
- "step_count": self._state.step_count,
166
- "llm": self._llm_status(),
167
- }
168
- )
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import uuid
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from district_llm.inference import DistrictLLMInference
9
+ from district_llm.schema import DistrictAction
10
+ from models import (
11
+ AgenticTrafficAction,
12
+ AgenticTrafficObservation,
13
+ AgenticTrafficState,
14
+ )
15
+ from openenv.core.env_server.interfaces import Environment
16
+ from openenv_app.openenv_wrapper import OpenEnvTrafficWrapper
17
+
18
+
19
+ REPO_ROOT = Path(__file__).resolve().parents[1]
20
+ DATA_DIR = Path(os.environ.get("DATA_DIR", "") or (REPO_ROOT / "data" / "generated"))
21
+ SPLITS_DIR = Path(os.environ.get("SPLITS_DIR", "") or (REPO_ROOT / "data" / "splits"))
22
+ DISTRICT_LLM_ADAPTER_PATH = Path(
23
+ os.environ.get("DISTRICT_LLM_ADAPTER_PATH", "")
24
+ or (REPO_ROOT / "artifacts" / "district_llm_adapter_v3" / "main_run" / "adapter")
25
+ )
26
+ DISTRICT_LLM_DEVICE = os.environ.get("DISTRICT_LLM_DEVICE")
27
+
28
+
29
+ class AgenticTrafficEnvironment(
30
+ Environment[AgenticTrafficAction, AgenticTrafficObservation, AgenticTrafficState]
31
+ ):
32
+ """Minimal OpenEnv-compatible wrapper around the existing district controller stack."""
33
+
34
+ def __init__(self) -> None:
35
+ super().__init__()
36
+ self.wrapper = OpenEnvTrafficWrapper(
37
+ generated_root=DATA_DIR,
38
+ splits_root=SPLITS_DIR,
39
+ )
40
+ self._state = AgenticTrafficState()
41
+ self._llm_inference: DistrictLLMInference | None = None
42
+ self._llm_load_attempted = False
43
+ self._llm_error: str | None = None
44
+
45
+ def reset(
46
+ self,
47
+ seed: int | None = None,
48
+ episode_id: str | None = None,
49
+ **kwargs: Any,
50
+ ) -> AgenticTrafficObservation:
51
+ payload = self.wrapper.reset(
52
+ seed=seed,
53
+ city_id=kwargs.get("city_id"),
54
+ scenario_name=kwargs.get("scenario_name"),
55
+ )
56
+ self._state.episode_id = episode_id or str(uuid.uuid4())
57
+ self._state.step_count = 0
58
+ self._sync_state()
59
+ observation = AgenticTrafficObservation.model_validate(payload["observation"])
60
+ observation.reward = None
61
+ observation.done = False
62
+ observation.metadata["llm"] = self._llm_status()
63
+ return observation
64
+
65
+ def step(
66
+ self,
67
+ action: AgenticTrafficAction,
68
+ timeout_s: float | None = None,
69
+ **kwargs: Any,
70
+ ) -> AgenticTrafficObservation:
71
+ del timeout_s, kwargs
72
+ payload = self.wrapper.step(action=self._build_step_payload(action))
73
+ self._state.step_count += 1
74
+ self._sync_state()
75
+ observation = AgenticTrafficObservation.model_validate(payload["observation"])
76
+ observation.done = bool(payload.get("done", False))
77
+ observation.reward = float(payload.get("reward", 0.0))
78
+ observation.metadata["llm"] = self._llm_status()
79
+ return observation
80
+
81
+ @property
82
+ def state(self) -> AgenticTrafficState:
83
+ self._sync_state()
84
+ return self._state
85
+
86
+ def _build_step_payload(self, action: AgenticTrafficAction) -> dict[str, Any]:
87
+ district_actions = dict(action.district_actions)
88
+ llm_generated_actions: dict[str, Any] = {}
89
+
90
+ if action.use_llm:
91
+ llm_generated_actions = self._generate_llm_actions(
92
+ existing_actions=district_actions,
93
+ max_new_tokens=action.llm_max_new_tokens,
94
+ )
95
+ for district_id, directive in llm_generated_actions.items():
96
+ district_actions.setdefault(district_id, directive)
97
+
98
+ payload = {"district_actions": district_actions}
99
+ payload["metadata"] = {
100
+ "use_llm": bool(action.use_llm),
101
+ "llm_generated_districts": sorted(llm_generated_actions),
102
+ "llm": self._llm_status(),
103
+ }
104
+ return payload
105
+
106
+ def _generate_llm_actions(
107
+ self,
108
+ existing_actions: dict[str, Any],
109
+ max_new_tokens: int,
110
+ ) -> dict[str, Any]:
111
+ if not self.wrapper.last_summaries:
112
+ return {}
113
+
114
+ inference = self._get_llm_inference()
115
+ if inference is None:
116
+ return {}
117
+
118
+ generated_actions: dict[str, Any] = {}
119
+ for district_id, summary in self.wrapper.last_summaries.items():
120
+ if district_id in existing_actions:
121
+ continue
122
+ result = inference.predict_with_result(summary=summary, max_new_tokens=max_new_tokens)
123
+ generated_actions[district_id] = result.action.to_dict()
124
+ return generated_actions
125
+
126
+ def _get_llm_inference(self) -> DistrictLLMInference | None:
127
+ if self._llm_inference is not None:
128
+ return self._llm_inference
129
+ if self._llm_load_attempted:
130
+ return None
131
+
132
+ self._llm_load_attempted = True
133
+ if not DISTRICT_LLM_ADAPTER_PATH.exists():
134
+ self._llm_error = f"Adapter not found at {DISTRICT_LLM_ADAPTER_PATH}"
135
+ return None
136
+
137
+ try:
138
+ self._llm_inference = DistrictLLMInference(
139
+ model_name_or_path=str(DISTRICT_LLM_ADAPTER_PATH),
140
+ device=DISTRICT_LLM_DEVICE,
141
+ fallback_action=DistrictAction.default_hold(
142
+ duration_steps=self.wrapper.district_decision_interval
143
+ ),
144
+ )
145
+ except Exception as exc:
146
+ self._llm_error = f"{type(exc).__name__}: {exc}"
147
+ self._llm_inference = None
148
+ return self._llm_inference
149
+
150
+ def _llm_status(self) -> dict[str, Any]:
151
+ return {
152
+ "adapter_path": str(DISTRICT_LLM_ADAPTER_PATH),
153
+ "adapter_present": DISTRICT_LLM_ADAPTER_PATH.exists(),
154
+ "loaded": self._llm_inference is not None,
155
+ "load_attempted": self._llm_load_attempted,
156
+ "error": self._llm_error,
157
+ }
158
+
159
+ def _sync_state(self) -> None:
160
+ payload = self.wrapper.state()["state"]
161
+ self._state = AgenticTrafficState.model_validate(
162
+ {
163
+ **payload,
164
+ "episode_id": self._state.episode_id,
165
+ "step_count": self._state.step_count,
166
+ "llm": self._llm_status(),
167
+ }
168
+ )
server/remote_runner.py CHANGED
@@ -1,58 +1,58 @@
1
- """HTTP client that delegates simulation runs to a remote OpenEnv API Space."""
2
- from __future__ import annotations
3
-
4
- import json
5
- import logging
6
- from pathlib import Path
7
- from typing import Any
8
-
9
- import httpx
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- def run_policy_remote(
15
- city_id: str,
16
- scenario_name: str,
17
- policy_name: str,
18
- openenv_api_url: str,
19
- output_root: Path,
20
- timeout: float = 120.0,
21
- ):
22
- """Call Space 1's /replay endpoint and write results to output_root."""
23
- from server.policy_runner import RunResult
24
-
25
- url = f"{openenv_api_url.rstrip('/')}/replay/{city_id}/{scenario_name}/{policy_name}"
26
- logger.info("Remote replay request: %s", url)
27
-
28
- with httpx.Client(timeout=timeout) as client:
29
- resp = client.get(url)
30
- resp.raise_for_status()
31
-
32
- payload: dict[str, Any] = resp.json()
33
-
34
- output_dir = output_root / city_id / scenario_name / policy_name
35
- output_dir.mkdir(parents=True, exist_ok=True)
36
-
37
- replay_path = output_dir / "replay.txt"
38
- replay_path.write_text(payload["replay_text"], encoding="utf-8")
39
-
40
- roadnet_log_path = output_dir / "roadnetLogFile.json"
41
- if payload.get("roadnet_log"):
42
- roadnet_log_path.write_text(
43
- json.dumps(payload["roadnet_log"], indent=2), encoding="utf-8"
44
- )
45
-
46
- metrics_path = output_dir / "metrics.json"
47
- metrics_path.write_text(
48
- json.dumps(payload.get("metrics", {}), indent=2), encoding="utf-8"
49
- )
50
-
51
- return RunResult(
52
- city_id=city_id,
53
- scenario_name=scenario_name,
54
- policy_name=policy_name,
55
- replay_path=replay_path,
56
- roadnet_log_path=roadnet_log_path,
57
- metrics=payload.get("metrics", {}),
58
- )
 
1
+ """HTTP client that delegates simulation runs to a remote OpenEnv API Space."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import httpx
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def run_policy_remote(
15
+ city_id: str,
16
+ scenario_name: str,
17
+ policy_name: str,
18
+ openenv_api_url: str,
19
+ output_root: Path,
20
+ timeout: float = 120.0,
21
+ ):
22
+ """Call Space 1's /replay endpoint and write results to output_root."""
23
+ from server.policy_runner import RunResult
24
+
25
+ url = f"{openenv_api_url.rstrip('/')}/replay/{city_id}/{scenario_name}/{policy_name}"
26
+ logger.info("Remote replay request: %s", url)
27
+
28
+ with httpx.Client(timeout=timeout) as client:
29
+ resp = client.get(url)
30
+ resp.raise_for_status()
31
+
32
+ payload: dict[str, Any] = resp.json()
33
+
34
+ output_dir = output_root / city_id / scenario_name / policy_name
35
+ output_dir.mkdir(parents=True, exist_ok=True)
36
+
37
+ replay_path = output_dir / "replay.txt"
38
+ replay_path.write_text(payload["replay_text"], encoding="utf-8")
39
+
40
+ roadnet_log_path = output_dir / "roadnetLogFile.json"
41
+ if payload.get("roadnet_log"):
42
+ roadnet_log_path.write_text(
43
+ json.dumps(payload["roadnet_log"], indent=2), encoding="utf-8"
44
+ )
45
+
46
+ metrics_path = output_dir / "metrics.json"
47
+ metrics_path.write_text(
48
+ json.dumps(payload.get("metrics", {}), indent=2), encoding="utf-8"
49
+ )
50
+
51
+ return RunResult(
52
+ city_id=city_id,
53
+ scenario_name=scenario_name,
54
+ policy_name=policy_name,
55
+ replay_path=replay_path,
56
+ roadnet_log_path=roadnet_log_path,
57
+ metrics=payload.get("metrics", {}),
58
+ )
server/roadnet_matcher.py CHANGED
@@ -1,55 +1,55 @@
1
- """Match an uploaded roadnet.json to a known city in the dataset by fingerprint."""
2
- from __future__ import annotations
3
-
4
- import json
5
- from pathlib import Path
6
-
7
-
8
- def match_city_by_roadnet(roadnet_data: dict, generated_root: Path) -> str | None:
9
- """Return the city_id whose roadnet.json matches the uploaded data, or None."""
10
- uploaded_fp = _fingerprint(roadnet_data)
11
- if not uploaded_fp:
12
- return None
13
-
14
- for city_dir in sorted(generated_root.glob("city_*")):
15
- roadnet_path = city_dir / "roadnet.json"
16
- if not roadnet_path.exists():
17
- continue
18
- try:
19
- candidate = json.loads(roadnet_path.read_text())
20
- if _fingerprint(candidate) == uploaded_fp:
21
- return city_dir.name
22
- except Exception:
23
- continue
24
-
25
- return None
26
-
27
-
28
- def list_all_cities(generated_root: Path) -> list[str]:
29
- return sorted(
30
- d.name
31
- for d in generated_root.glob("city_*")
32
- if d.is_dir() and (d / "roadnet.json").exists()
33
- )
34
-
35
-
36
- def list_scenarios_for_city(city_id: str, generated_root: Path) -> list[str]:
37
- scenario_root = generated_root / city_id / "scenarios"
38
- if not scenario_root.exists():
39
- return []
40
- return sorted(
41
- d.name
42
- for d in scenario_root.iterdir()
43
- if d.is_dir()
44
- and (d / "config.json").exists()
45
- and (d / "flow.json").exists()
46
- )
47
-
48
-
49
- def _fingerprint(roadnet: dict) -> frozenset[str]:
50
- """Fingerprint = set of non-virtual intersection IDs."""
51
- return frozenset(
52
- item["id"]
53
- for item in roadnet.get("intersections", [])
54
- if not item.get("virtual", False) and item.get("id")
55
- )
 
1
+ """Match an uploaded roadnet.json to a known city in the dataset by fingerprint."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+
7
+
8
+ def match_city_by_roadnet(roadnet_data: dict, generated_root: Path) -> str | None:
9
+ """Return the city_id whose roadnet.json matches the uploaded data, or None."""
10
+ uploaded_fp = _fingerprint(roadnet_data)
11
+ if not uploaded_fp:
12
+ return None
13
+
14
+ for city_dir in sorted(generated_root.glob("city_*")):
15
+ roadnet_path = city_dir / "roadnet.json"
16
+ if not roadnet_path.exists():
17
+ continue
18
+ try:
19
+ candidate = json.loads(roadnet_path.read_text())
20
+ if _fingerprint(candidate) == uploaded_fp:
21
+ return city_dir.name
22
+ except Exception:
23
+ continue
24
+
25
+ return None
26
+
27
+
28
+ def list_all_cities(generated_root: Path) -> list[str]:
29
+ return sorted(
30
+ d.name
31
+ for d in generated_root.glob("city_*")
32
+ if d.is_dir() and (d / "roadnet.json").exists()
33
+ )
34
+
35
+
36
+ def list_scenarios_for_city(city_id: str, generated_root: Path) -> list[str]:
37
+ scenario_root = generated_root / city_id / "scenarios"
38
+ if not scenario_root.exists():
39
+ return []
40
+ return sorted(
41
+ d.name
42
+ for d in scenario_root.iterdir()
43
+ if d.is_dir()
44
+ and (d / "config.json").exists()
45
+ and (d / "flow.json").exists()
46
+ )
47
+
48
+
49
+ def _fingerprint(roadnet: dict) -> frozenset[str]:
50
+ """Fingerprint = set of non-virtual intersection IDs."""
51
+ return frozenset(
52
+ item["id"]
53
+ for item in roadnet.get("intersections", [])
54
+ if not item.get("virtual", False) and item.get("id")
55
+ )
server/visualizer_app.py CHANGED
@@ -1,404 +1,404 @@
1
- """FastAPI visualizer server for CityFlow multi-policy comparison dashboard.
2
-
3
- Deployment modes
4
- ----------------
5
- Local (no OPENENV_API_URL set):
6
- Runs CityFlow simulations in-process via ``server.policy_runner``.
7
-
8
- HF Space 2 (OPENENV_API_URL set to Space 1's URL):
9
- Delegates all simulation runs to the remote OpenEnv API via
10
- ``server.remote_runner``. No CityFlow or torch needed locally.
11
- """
12
- from __future__ import annotations
13
-
14
- import json
15
- import logging
16
- import os
17
- import sys
18
- import time
19
- from contextlib import asynccontextmanager
20
- from pathlib import Path
21
- from typing import Any
22
-
23
- import httpx
24
- from fastapi import FastAPI, HTTPException, UploadFile
25
- from fastapi.middleware.cors import CORSMiddleware
26
- from fastapi.responses import FileResponse, JSONResponse, PlainTextResponse
27
- from fastapi.staticfiles import StaticFiles
28
- from joblib import Parallel, delayed
29
- from pydantic import BaseModel
30
-
31
- REPO_ROOT = Path(__file__).resolve().parents[1]
32
- if str(REPO_ROOT) not in sys.path:
33
- sys.path.insert(0, str(REPO_ROOT))
34
-
35
- logger = logging.getLogger(__name__)
36
-
37
- # ---------------------------------------------------------------------------
38
- # Configuration — all overridable via environment variables
39
- # ---------------------------------------------------------------------------
40
-
41
- OPENENV_API_URL: str | None = os.environ.get("OPENENV_API_URL") or None
42
-
43
- GENERATED_ROOT = Path(
44
- os.environ.get("DATA_DIR", "") or (REPO_ROOT / "data" / "generated")
45
- )
46
- REPLAY_OUTPUT_ROOT = Path(
47
- os.environ.get("REPLAY_ROOT", "") or (REPO_ROOT / "results" / "replays")
48
- )
49
- CHECKPOINT_PATH = Path(
50
- os.environ.get("CHECKPOINT_PATH", "")
51
- or (REPO_ROOT / "artifacts" / "dqn_shared" / "best_validation.pt")
52
- )
53
- FRONTEND_DIR = REPO_ROOT / "third_party" / "CityFlow" / "frontend"
54
-
55
- # ---------------------------------------------------------------------------
56
- # Runner selection: local (policy_runner) vs. remote (remote_runner)
57
- # ---------------------------------------------------------------------------
58
-
59
- from server.path_validators import validate_path_segment
60
- from server.policy_runner import ALL_POLICIES, RunResult
61
-
62
- if OPENENV_API_URL:
63
- logger.info("Remote mode — OpenEnv API at %s", OPENENV_API_URL)
64
- from server.remote_runner import run_policy_remote as _run_remote
65
-
66
- def _run_policy(city_id: str, scenario_name: str, policy_name: str) -> RunResult:
67
- return _run_remote(
68
- city_id=city_id,
69
- scenario_name=scenario_name,
70
- policy_name=policy_name,
71
- openenv_api_url=OPENENV_API_URL, # type: ignore[arg-type]
72
- output_root=REPLAY_OUTPUT_ROOT,
73
- )
74
-
75
- else:
76
- logger.info("Local mode — running CityFlow in-process")
77
- from server.policy_runner import run_policy_for_city as _run_local
78
-
79
- def _run_policy(city_id: str, scenario_name: str, policy_name: str) -> RunResult:
80
- return _run_local(
81
- city_id=city_id,
82
- scenario_name=scenario_name,
83
- policy_name=policy_name,
84
- generated_root=GENERATED_ROOT,
85
- output_root=REPLAY_OUTPUT_ROOT,
86
- )
87
-
88
-
89
- from server.roadnet_matcher import (
90
- list_all_cities,
91
- list_scenarios_for_city,
92
- match_city_by_roadnet,
93
- )
94
-
95
- # ---------------------------------------------------------------------------
96
- # Lifespan
97
- # ---------------------------------------------------------------------------
98
-
99
-
100
- @asynccontextmanager
101
- async def lifespan(app: FastAPI):
102
- REPLAY_OUTPUT_ROOT.mkdir(parents=True, exist_ok=True)
103
-
104
- if OPENENV_API_URL:
105
- try:
106
- with httpx.Client(timeout=10.0) as client:
107
- resp = client.get(f"{OPENENV_API_URL.rstrip('/')}/health")
108
- resp.raise_for_status()
109
- logger.info("OpenEnv API health check passed: %s", OPENENV_API_URL)
110
- except Exception as exc:
111
- logger.warning(
112
- "OpenEnv API at %s did not respond to /health: %s. "
113
- "Simulation requests will fail until it is reachable.",
114
- OPENENV_API_URL,
115
- exc,
116
- )
117
- else:
118
- from server.policy_runner import load_district_llm_inference, load_dqn_checkpoint
119
- if CHECKPOINT_PATH.exists():
120
- load_dqn_checkpoint(CHECKPOINT_PATH)
121
- else:
122
- logger.warning("Checkpoint not found at %s — 'learned' policy will fail", CHECKPOINT_PATH)
123
- try:
124
- load_district_llm_inference()
125
- except Exception as exc:
126
- logger.warning(
127
- "District LLM prewarm failed: %s. "
128
- "The llm_dqn policy will retry loading lazily on first use.",
129
- exc,
130
- )
131
-
132
- yield
133
-
134
- if not OPENENV_API_URL:
135
- from server.policy_runner import unload_district_llm_inference
136
-
137
- try:
138
- unload_district_llm_inference()
139
- except Exception as exc:
140
- logger.warning("District LLM unload failed during shutdown: %s", exc)
141
-
142
-
143
- # ---------------------------------------------------------------------------
144
- # App setup
145
- # ---------------------------------------------------------------------------
146
-
147
- app = FastAPI(
148
- title="Traffic Visualizer",
149
- description="Multi-policy CityFlow replay comparison dashboard.",
150
- version="1.0.0",
151
- lifespan=lifespan,
152
- )
153
-
154
- app.add_middleware(
155
- CORSMiddleware,
156
- allow_origins=["*"],
157
- allow_methods=["*"],
158
- allow_headers=["*"],
159
- )
160
-
161
- app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR)), name="frontend")
162
-
163
- # ---------------------------------------------------------------------------
164
- # Request / response models
165
- # ---------------------------------------------------------------------------
166
-
167
-
168
- class RunSimulationsRequest(BaseModel):
169
- city_id: str
170
- scenario_name: str
171
- policies: list[str] = list(ALL_POLICIES)
172
- force: bool = False
173
-
174
-
175
- class PolicyMetrics(BaseModel):
176
- policy_name: str
177
- metrics: dict[str, Any]
178
- replay_available: bool
179
- roadnet_log_available: bool
180
- elapsed_ms: float | None = None
181
-
182
-
183
- class RunSimulationsResponse(BaseModel):
184
- city_id: str
185
- scenario_name: str
186
- results: list[PolicyMetrics]
187
-
188
-
189
- # ---------------------------------------------------------------------------
190
- # Endpoints
191
- # ---------------------------------------------------------------------------
192
-
193
-
194
- @app.get("/")
195
- def root():
196
- index_path = FRONTEND_DIR / "index.html"
197
- if index_path.exists():
198
- return FileResponse(str(index_path))
199
- return JSONResponse({"status": "Traffic Visualizer API running"})
200
-
201
-
202
- @app.post("/upload-roadnet")
203
- async def upload_roadnet(file: UploadFile) -> dict:
204
- raw = await file.read()
205
- try:
206
- roadnet_data = json.loads(raw)
207
- except json.JSONDecodeError as exc:
208
- raise HTTPException(status_code=400, detail=f"Invalid JSON: {exc}")
209
-
210
- city_id = match_city_by_roadnet(roadnet_data, GENERATED_ROOT)
211
- if city_id is None:
212
- return {
213
- "matched": False,
214
- "city_id": None,
215
- "scenarios": [],
216
- "all_cities": list_all_cities(GENERATED_ROOT),
217
- }
218
-
219
- scenarios = list_scenarios_for_city(city_id, GENERATED_ROOT)
220
- return {"matched": True, "city_id": city_id, "scenarios": scenarios, "all_cities": []}
221
-
222
-
223
- @app.get("/cities")
224
- def get_cities() -> dict:
225
- return {"cities": list_all_cities(GENERATED_ROOT)}
226
-
227
-
228
- @app.get("/cities/{city_id}/scenarios")
229
- def get_scenarios(city_id: str) -> dict:
230
- validate_path_segment(city_id, "city_id")
231
- scenarios = list_scenarios_for_city(city_id, GENERATED_ROOT)
232
- if not scenarios:
233
- raise HTTPException(
234
- status_code=404,
235
- detail=f"City '{city_id}' not found or has no scenarios.",
236
- )
237
- return {"city_id": city_id, "scenarios": scenarios}
238
-
239
-
240
- @app.get("/cities/{city_id}/district-map")
241
- def get_district_map(city_id: str) -> JSONResponse:
242
- validate_path_segment(city_id, "city_id")
243
- district_map_path = GENERATED_ROOT / city_id / "district_map.json"
244
- if not district_map_path.exists():
245
- raise HTTPException(
246
- status_code=404,
247
- detail=f"District map not found for city '{city_id}'.",
248
- )
249
- return JSONResponse(json.loads(district_map_path.read_text(encoding="utf-8")))
250
-
251
-
252
- @app.post("/run-simulations", response_model=RunSimulationsResponse)
253
- def run_simulations(request: RunSimulationsRequest) -> RunSimulationsResponse:
254
- validate_path_segment(request.city_id, "city_id")
255
- validate_path_segment(request.scenario_name, "scenario_name")
256
-
257
- valid_policies = set(ALL_POLICIES)
258
- bad = [p for p in request.policies if p not in valid_policies]
259
- if bad:
260
- raise HTTPException(
261
- status_code=400,
262
- detail=f"Unknown policies: {bad}. Valid: {list(ALL_POLICIES)}",
263
- )
264
-
265
- def _run_one(policy_name: str) -> PolicyMetrics:
266
- started_at = time.perf_counter()
267
- output_dir = REPLAY_OUTPUT_ROOT / request.city_id / request.scenario_name / policy_name
268
- replay_path = output_dir / "replay.txt"
269
- roadnet_path = output_dir / "roadnetLogFile.json"
270
- metrics_path = output_dir / "metrics.json"
271
-
272
- if not request.force and replay_path.exists() and metrics_path.exists():
273
- return PolicyMetrics(
274
- policy_name=policy_name,
275
- metrics=json.loads(metrics_path.read_text(encoding="utf-8")),
276
- replay_available=True,
277
- roadnet_log_available=roadnet_path.exists(),
278
- elapsed_ms=0.0,
279
- )
280
-
281
- try:
282
- result: RunResult = _run_policy(
283
- city_id=request.city_id,
284
- scenario_name=request.scenario_name,
285
- policy_name=policy_name,
286
- )
287
- return PolicyMetrics(
288
- policy_name=policy_name,
289
- metrics=result.metrics,
290
- replay_available=result.replay_path.exists(),
291
- roadnet_log_available=result.roadnet_log_path.exists(),
292
- elapsed_ms=(time.perf_counter() - started_at) * 1000.0,
293
- )
294
- except Exception as exc:
295
- logger.error("Policy run failed for %s/%s/%s: %s", request.city_id, request.scenario_name, policy_name, exc)
296
- return PolicyMetrics(
297
- policy_name=policy_name,
298
- metrics={"error": "Simulation failed. Check server logs."},
299
- replay_available=False,
300
- roadnet_log_available=False,
301
- elapsed_ms=(time.perf_counter() - started_at) * 1000.0,
302
- )
303
-
304
- n_jobs = min(len(request.policies), 4)
305
- results: list[PolicyMetrics] = Parallel(
306
- n_jobs=n_jobs, prefer="threads"
307
- )(delayed(_run_one)(p) for p in request.policies)
308
-
309
- return RunSimulationsResponse(
310
- city_id=request.city_id,
311
- scenario_name=request.scenario_name,
312
- results=results,
313
- )
314
-
315
-
316
- @app.get("/replay/{city_id}/{scenario_name}/{policy_name}", response_model=None)
317
- def get_replay(
318
- city_id: str,
319
- scenario_name: str,
320
- policy_name: str,
321
- max_steps: int = 0,
322
- ) -> PlainTextResponse | FileResponse:
323
- validate_path_segment(city_id, "city_id")
324
- validate_path_segment(scenario_name, "scenario_name")
325
- validate_path_segment(policy_name, "policy_name")
326
-
327
- replay_path = REPLAY_OUTPUT_ROOT / city_id / scenario_name / policy_name / "replay.txt"
328
- if not replay_path.exists():
329
- raise HTTPException(
330
- status_code=404,
331
- detail=f"Replay not found for {city_id}/{scenario_name}/{policy_name}. Run /run-simulations first.",
332
- )
333
- if max_steps > 0:
334
- lines: list[str] = []
335
- with open(replay_path, encoding="utf-8") as fh:
336
- for raw in fh:
337
- if raw.strip():
338
- lines.append(raw.rstrip("\n"))
339
- if len(lines) >= max_steps:
340
- break
341
- return PlainTextResponse("\n".join(lines))
342
- return FileResponse(str(replay_path), media_type="text/plain")
343
-
344
-
345
- @app.get("/roadnet-log/{city_id}/{scenario_name}/{policy_name}")
346
- def get_roadnet_log(city_id: str, scenario_name: str, policy_name: str) -> JSONResponse:
347
- validate_path_segment(city_id, "city_id")
348
- validate_path_segment(scenario_name, "scenario_name")
349
- validate_path_segment(policy_name, "policy_name")
350
-
351
- path = REPLAY_OUTPUT_ROOT / city_id / scenario_name / policy_name / "roadnetLogFile.json"
352
- if not path.exists():
353
- for p in ALL_POLICIES:
354
- fallback = REPLAY_OUTPUT_ROOT / city_id / scenario_name / p / "roadnetLogFile.json"
355
- if fallback.exists():
356
- path = fallback
357
- break
358
- if not path.exists():
359
- raise HTTPException(
360
- status_code=404,
361
- detail=f"Roadnet log not found for {city_id}/{scenario_name}.",
362
- )
363
- return JSONResponse(json.loads(path.read_text(encoding="utf-8")))
364
-
365
-
366
- @app.get("/metrics/{city_id}/{scenario_name}")
367
- def get_metrics(city_id: str, scenario_name: str) -> dict:
368
- validate_path_segment(city_id, "city_id")
369
- validate_path_segment(scenario_name, "scenario_name")
370
-
371
- base = REPLAY_OUTPUT_ROOT / city_id / scenario_name
372
- if not base.exists():
373
- raise HTTPException(
374
- status_code=404,
375
- detail=f"No simulation results found for {city_id}/{scenario_name}.",
376
- )
377
-
378
- metrics: dict[str, Any] = {}
379
- for policy_dir in sorted(base.iterdir()):
380
- if not policy_dir.is_dir():
381
- continue
382
- metrics_path = policy_dir / "metrics.json"
383
- replay_path = policy_dir / "replay.txt"
384
- roadnet_log_path = policy_dir / "roadnetLogFile.json"
385
- payload: dict[str, Any] = {}
386
- if metrics_path.exists():
387
- payload.update(json.loads(metrics_path.read_text(encoding="utf-8")))
388
- if replay_path.exists():
389
- payload["replay_available"] = True
390
- if roadnet_log_path.exists():
391
- payload["roadnet_log_available"] = True
392
- if payload:
393
- metrics[policy_dir.name] = payload
394
-
395
- return {"city_id": city_id, "scenario_name": scenario_name, "metrics": metrics}
396
-
397
-
398
- # ---------------------------------------------------------------------------
399
- # Entry point
400
- # ---------------------------------------------------------------------------
401
-
402
- if __name__ == "__main__":
403
- import uvicorn
404
- uvicorn.run("server.visualizer_app:app", host="0.0.0.0", port=8080, reload=False)
 
1
+ """FastAPI visualizer server for CityFlow multi-policy comparison dashboard.
2
+
3
+ Deployment modes
4
+ ----------------
5
+ Local (no OPENENV_API_URL set):
6
+ Runs CityFlow simulations in-process via ``server.policy_runner``.
7
+
8
+ HF Space 2 (OPENENV_API_URL set to Space 1's URL):
9
+ Delegates all simulation runs to the remote OpenEnv API via
10
+ ``server.remote_runner``. No CityFlow or torch needed locally.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import logging
16
+ import os
17
+ import sys
18
+ import time
19
+ from contextlib import asynccontextmanager
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ import httpx
24
+ from fastapi import FastAPI, HTTPException, UploadFile
25
+ from fastapi.middleware.cors import CORSMiddleware
26
+ from fastapi.responses import FileResponse, JSONResponse, PlainTextResponse
27
+ from fastapi.staticfiles import StaticFiles
28
+ from joblib import Parallel, delayed
29
+ from pydantic import BaseModel
30
+
31
+ REPO_ROOT = Path(__file__).resolve().parents[1]
32
+ if str(REPO_ROOT) not in sys.path:
33
+ sys.path.insert(0, str(REPO_ROOT))
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # Configuration — all overridable via environment variables
39
+ # ---------------------------------------------------------------------------
40
+
41
+ OPENENV_API_URL: str | None = os.environ.get("OPENENV_API_URL") or None
42
+
43
+ GENERATED_ROOT = Path(
44
+ os.environ.get("DATA_DIR", "") or (REPO_ROOT / "data" / "generated")
45
+ )
46
+ REPLAY_OUTPUT_ROOT = Path(
47
+ os.environ.get("REPLAY_ROOT", "") or (REPO_ROOT / "results" / "replays")
48
+ )
49
+ CHECKPOINT_PATH = Path(
50
+ os.environ.get("CHECKPOINT_PATH", "")
51
+ or (REPO_ROOT / "artifacts" / "dqn_shared" / "best_validation.pt")
52
+ )
53
+ FRONTEND_DIR = REPO_ROOT / "third_party" / "CityFlow" / "frontend"
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Runner selection: local (policy_runner) vs. remote (remote_runner)
57
+ # ---------------------------------------------------------------------------
58
+
59
+ from server.path_validators import validate_path_segment
60
+ from server.policy_runner import ALL_POLICIES, RunResult
61
+
62
+ if OPENENV_API_URL:
63
+ logger.info("Remote mode — OpenEnv API at %s", OPENENV_API_URL)
64
+ from server.remote_runner import run_policy_remote as _run_remote
65
+
66
+ def _run_policy(city_id: str, scenario_name: str, policy_name: str) -> RunResult:
67
+ return _run_remote(
68
+ city_id=city_id,
69
+ scenario_name=scenario_name,
70
+ policy_name=policy_name,
71
+ openenv_api_url=OPENENV_API_URL, # type: ignore[arg-type]
72
+ output_root=REPLAY_OUTPUT_ROOT,
73
+ )
74
+
75
+ else:
76
+ logger.info("Local mode — running CityFlow in-process")
77
+ from server.policy_runner import run_policy_for_city as _run_local
78
+
79
+ def _run_policy(city_id: str, scenario_name: str, policy_name: str) -> RunResult:
80
+ return _run_local(
81
+ city_id=city_id,
82
+ scenario_name=scenario_name,
83
+ policy_name=policy_name,
84
+ generated_root=GENERATED_ROOT,
85
+ output_root=REPLAY_OUTPUT_ROOT,
86
+ )
87
+
88
+
89
+ from server.roadnet_matcher import (
90
+ list_all_cities,
91
+ list_scenarios_for_city,
92
+ match_city_by_roadnet,
93
+ )
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # Lifespan
97
+ # ---------------------------------------------------------------------------
98
+
99
+
100
+ @asynccontextmanager
101
+ async def lifespan(app: FastAPI):
102
+ REPLAY_OUTPUT_ROOT.mkdir(parents=True, exist_ok=True)
103
+
104
+ if OPENENV_API_URL:
105
+ try:
106
+ with httpx.Client(timeout=10.0) as client:
107
+ resp = client.get(f"{OPENENV_API_URL.rstrip('/')}/health")
108
+ resp.raise_for_status()
109
+ logger.info("OpenEnv API health check passed: %s", OPENENV_API_URL)
110
+ except Exception as exc:
111
+ logger.warning(
112
+ "OpenEnv API at %s did not respond to /health: %s. "
113
+ "Simulation requests will fail until it is reachable.",
114
+ OPENENV_API_URL,
115
+ exc,
116
+ )
117
+ else:
118
+ from server.policy_runner import load_district_llm_inference, load_dqn_checkpoint
119
+ if CHECKPOINT_PATH.exists():
120
+ load_dqn_checkpoint(CHECKPOINT_PATH)
121
+ else:
122
+ logger.warning("Checkpoint not found at %s — 'learned' policy will fail", CHECKPOINT_PATH)
123
+ try:
124
+ load_district_llm_inference()
125
+ except Exception as exc:
126
+ logger.warning(
127
+ "District LLM prewarm failed: %s. "
128
+ "The llm_dqn policy will retry loading lazily on first use.",
129
+ exc,
130
+ )
131
+
132
+ yield
133
+
134
+ if not OPENENV_API_URL:
135
+ from server.policy_runner import unload_district_llm_inference
136
+
137
+ try:
138
+ unload_district_llm_inference()
139
+ except Exception as exc:
140
+ logger.warning("District LLM unload failed during shutdown: %s", exc)
141
+
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # App setup
145
+ # ---------------------------------------------------------------------------
146
+
147
+ app = FastAPI(
148
+ title="Traffic Visualizer",
149
+ description="Multi-policy CityFlow replay comparison dashboard.",
150
+ version="1.0.0",
151
+ lifespan=lifespan,
152
+ )
153
+
154
+ app.add_middleware(
155
+ CORSMiddleware,
156
+ allow_origins=["*"],
157
+ allow_methods=["*"],
158
+ allow_headers=["*"],
159
+ )
160
+
161
+ app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR)), name="frontend")
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # Request / response models
165
+ # ---------------------------------------------------------------------------
166
+
167
+
168
+ class RunSimulationsRequest(BaseModel):
169
+ city_id: str
170
+ scenario_name: str
171
+ policies: list[str] = list(ALL_POLICIES)
172
+ force: bool = False
173
+
174
+
175
+ class PolicyMetrics(BaseModel):
176
+ policy_name: str
177
+ metrics: dict[str, Any]
178
+ replay_available: bool
179
+ roadnet_log_available: bool
180
+ elapsed_ms: float | None = None
181
+
182
+
183
+ class RunSimulationsResponse(BaseModel):
184
+ city_id: str
185
+ scenario_name: str
186
+ results: list[PolicyMetrics]
187
+
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # Endpoints
191
+ # ---------------------------------------------------------------------------
192
+
193
+
194
+ @app.get("/")
195
+ def root():
196
+ index_path = FRONTEND_DIR / "index.html"
197
+ if index_path.exists():
198
+ return FileResponse(str(index_path))
199
+ return JSONResponse({"status": "Traffic Visualizer API running"})
200
+
201
+
202
+ @app.post("/upload-roadnet")
203
+ async def upload_roadnet(file: UploadFile) -> dict:
204
+ raw = await file.read()
205
+ try:
206
+ roadnet_data = json.loads(raw)
207
+ except json.JSONDecodeError as exc:
208
+ raise HTTPException(status_code=400, detail=f"Invalid JSON: {exc}")
209
+
210
+ city_id = match_city_by_roadnet(roadnet_data, GENERATED_ROOT)
211
+ if city_id is None:
212
+ return {
213
+ "matched": False,
214
+ "city_id": None,
215
+ "scenarios": [],
216
+ "all_cities": list_all_cities(GENERATED_ROOT),
217
+ }
218
+
219
+ scenarios = list_scenarios_for_city(city_id, GENERATED_ROOT)
220
+ return {"matched": True, "city_id": city_id, "scenarios": scenarios, "all_cities": []}
221
+
222
+
223
+ @app.get("/cities")
224
+ def get_cities() -> dict:
225
+ return {"cities": list_all_cities(GENERATED_ROOT)}
226
+
227
+
228
+ @app.get("/cities/{city_id}/scenarios")
229
+ def get_scenarios(city_id: str) -> dict:
230
+ validate_path_segment(city_id, "city_id")
231
+ scenarios = list_scenarios_for_city(city_id, GENERATED_ROOT)
232
+ if not scenarios:
233
+ raise HTTPException(
234
+ status_code=404,
235
+ detail=f"City '{city_id}' not found or has no scenarios.",
236
+ )
237
+ return {"city_id": city_id, "scenarios": scenarios}
238
+
239
+
240
+ @app.get("/cities/{city_id}/district-map")
241
+ def get_district_map(city_id: str) -> JSONResponse:
242
+ validate_path_segment(city_id, "city_id")
243
+ district_map_path = GENERATED_ROOT / city_id / "district_map.json"
244
+ if not district_map_path.exists():
245
+ raise HTTPException(
246
+ status_code=404,
247
+ detail=f"District map not found for city '{city_id}'.",
248
+ )
249
+ return JSONResponse(json.loads(district_map_path.read_text(encoding="utf-8")))
250
+
251
+
252
+ @app.post("/run-simulations", response_model=RunSimulationsResponse)
253
+ def run_simulations(request: RunSimulationsRequest) -> RunSimulationsResponse:
254
+ validate_path_segment(request.city_id, "city_id")
255
+ validate_path_segment(request.scenario_name, "scenario_name")
256
+
257
+ valid_policies = set(ALL_POLICIES)
258
+ bad = [p for p in request.policies if p not in valid_policies]
259
+ if bad:
260
+ raise HTTPException(
261
+ status_code=400,
262
+ detail=f"Unknown policies: {bad}. Valid: {list(ALL_POLICIES)}",
263
+ )
264
+
265
+ def _run_one(policy_name: str) -> PolicyMetrics:
266
+ started_at = time.perf_counter()
267
+ output_dir = REPLAY_OUTPUT_ROOT / request.city_id / request.scenario_name / policy_name
268
+ replay_path = output_dir / "replay.txt"
269
+ roadnet_path = output_dir / "roadnetLogFile.json"
270
+ metrics_path = output_dir / "metrics.json"
271
+
272
+ if not request.force and replay_path.exists() and metrics_path.exists():
273
+ return PolicyMetrics(
274
+ policy_name=policy_name,
275
+ metrics=json.loads(metrics_path.read_text(encoding="utf-8")),
276
+ replay_available=True,
277
+ roadnet_log_available=roadnet_path.exists(),
278
+ elapsed_ms=0.0,
279
+ )
280
+
281
+ try:
282
+ result: RunResult = _run_policy(
283
+ city_id=request.city_id,
284
+ scenario_name=request.scenario_name,
285
+ policy_name=policy_name,
286
+ )
287
+ return PolicyMetrics(
288
+ policy_name=policy_name,
289
+ metrics=result.metrics,
290
+ replay_available=result.replay_path.exists(),
291
+ roadnet_log_available=result.roadnet_log_path.exists(),
292
+ elapsed_ms=(time.perf_counter() - started_at) * 1000.0,
293
+ )
294
+ except Exception as exc:
295
+ logger.error("Policy run failed for %s/%s/%s: %s", request.city_id, request.scenario_name, policy_name, exc)
296
+ return PolicyMetrics(
297
+ policy_name=policy_name,
298
+ metrics={"error": "Simulation failed. Check server logs."},
299
+ replay_available=False,
300
+ roadnet_log_available=False,
301
+ elapsed_ms=(time.perf_counter() - started_at) * 1000.0,
302
+ )
303
+
304
+ n_jobs = min(len(request.policies), 4)
305
+ results: list[PolicyMetrics] = Parallel(
306
+ n_jobs=n_jobs, prefer="threads"
307
+ )(delayed(_run_one)(p) for p in request.policies)
308
+
309
+ return RunSimulationsResponse(
310
+ city_id=request.city_id,
311
+ scenario_name=request.scenario_name,
312
+ results=results,
313
+ )
314
+
315
+
316
+ @app.get("/replay/{city_id}/{scenario_name}/{policy_name}", response_model=None)
317
+ def get_replay(
318
+ city_id: str,
319
+ scenario_name: str,
320
+ policy_name: str,
321
+ max_steps: int = 0,
322
+ ) -> PlainTextResponse | FileResponse:
323
+ validate_path_segment(city_id, "city_id")
324
+ validate_path_segment(scenario_name, "scenario_name")
325
+ validate_path_segment(policy_name, "policy_name")
326
+
327
+ replay_path = REPLAY_OUTPUT_ROOT / city_id / scenario_name / policy_name / "replay.txt"
328
+ if not replay_path.exists():
329
+ raise HTTPException(
330
+ status_code=404,
331
+ detail=f"Replay not found for {city_id}/{scenario_name}/{policy_name}. Run /run-simulations first.",
332
+ )
333
+ if max_steps > 0:
334
+ lines: list[str] = []
335
+ with open(replay_path, encoding="utf-8") as fh:
336
+ for raw in fh:
337
+ if raw.strip():
338
+ lines.append(raw.rstrip("\n"))
339
+ if len(lines) >= max_steps:
340
+ break
341
+ return PlainTextResponse("\n".join(lines))
342
+ return FileResponse(str(replay_path), media_type="text/plain")
343
+
344
+
345
+ @app.get("/roadnet-log/{city_id}/{scenario_name}/{policy_name}")
346
+ def get_roadnet_log(city_id: str, scenario_name: str, policy_name: str) -> JSONResponse:
347
+ validate_path_segment(city_id, "city_id")
348
+ validate_path_segment(scenario_name, "scenario_name")
349
+ validate_path_segment(policy_name, "policy_name")
350
+
351
+ path = REPLAY_OUTPUT_ROOT / city_id / scenario_name / policy_name / "roadnetLogFile.json"
352
+ if not path.exists():
353
+ for p in ALL_POLICIES:
354
+ fallback = REPLAY_OUTPUT_ROOT / city_id / scenario_name / p / "roadnetLogFile.json"
355
+ if fallback.exists():
356
+ path = fallback
357
+ break
358
+ if not path.exists():
359
+ raise HTTPException(
360
+ status_code=404,
361
+ detail=f"Roadnet log not found for {city_id}/{scenario_name}.",
362
+ )
363
+ return JSONResponse(json.loads(path.read_text(encoding="utf-8")))
364
+
365
+
366
+ @app.get("/metrics/{city_id}/{scenario_name}")
367
+ def get_metrics(city_id: str, scenario_name: str) -> dict:
368
+ validate_path_segment(city_id, "city_id")
369
+ validate_path_segment(scenario_name, "scenario_name")
370
+
371
+ base = REPLAY_OUTPUT_ROOT / city_id / scenario_name
372
+ if not base.exists():
373
+ raise HTTPException(
374
+ status_code=404,
375
+ detail=f"No simulation results found for {city_id}/{scenario_name}.",
376
+ )
377
+
378
+ metrics: dict[str, Any] = {}
379
+ for policy_dir in sorted(base.iterdir()):
380
+ if not policy_dir.is_dir():
381
+ continue
382
+ metrics_path = policy_dir / "metrics.json"
383
+ replay_path = policy_dir / "replay.txt"
384
+ roadnet_log_path = policy_dir / "roadnetLogFile.json"
385
+ payload: dict[str, Any] = {}
386
+ if metrics_path.exists():
387
+ payload.update(json.loads(metrics_path.read_text(encoding="utf-8")))
388
+ if replay_path.exists():
389
+ payload["replay_available"] = True
390
+ if roadnet_log_path.exists():
391
+ payload["roadnet_log_available"] = True
392
+ if payload:
393
+ metrics[policy_dir.name] = payload
394
+
395
+ return {"city_id": city_id, "scenario_name": scenario_name, "metrics": metrics}
396
+
397
+
398
+ # ---------------------------------------------------------------------------
399
+ # Entry point
400
+ # ---------------------------------------------------------------------------
401
+
402
+ if __name__ == "__main__":
403
+ import uvicorn
404
+ uvicorn.run("server.visualizer_app:app", host="0.0.0.0", port=8080, reload=False)
third_party/CityFlow/.gitignore CHANGED
@@ -1,14 +1,14 @@
1
- data/
2
- build/
3
- build-docker/
4
- local/
5
- .vs/
6
- .vscode/
7
- .idea/
8
- .DS_Store
9
- __pycache__
10
- CMakeSettings.json
11
- cmake-build-*
12
- CityFlow.egg-info
13
- frontend/replay/*
14
  results/replays
 
1
+ data/
2
+ build/
3
+ build-docker/
4
+ local/
5
+ .vs/
6
+ .vscode/
7
+ .idea/
8
+ .DS_Store
9
+ __pycache__
10
+ CMakeSettings.json
11
+ cmake-build-*
12
+ CityFlow.egg-info
13
+ frontend/replay/*
14
  results/replays
third_party/CityFlow/.gitmodules CHANGED
@@ -1,6 +1,6 @@
1
- [submodule "extern/rapidjson"]
2
- path = extern/rapidjson
3
- url = https://github.com/cityflow-project/rapidjson.git
4
- [submodule "extern/pybind11"]
5
- path = extern/pybind11
6
- url = https://github.com/cityflow-project/pybind11.git
 
1
+ [submodule "extern/rapidjson"]
2
+ path = extern/rapidjson
3
+ url = https://github.com/cityflow-project/rapidjson.git
4
+ [submodule "extern/pybind11"]
5
+ path = extern/pybind11
6
+ url = https://github.com/cityflow-project/pybind11.git
third_party/CityFlow/CMakeLists.txt CHANGED
@@ -1,76 +1,76 @@
1
- cmake_minimum_required(VERSION 3.5)
2
- project(cityflow)
3
-
4
- set(CMAKE_CXX_STANDARD "11" CACHE STRING "")
5
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -DRAPIDJSON_HAS_STDSTRING=1")
6
- set(CMAKE_CXX_FLAGS_RELEASE "-O2")
7
- set(CMAKE_CXX_FLAGS_DEBUG "-g")
8
- set(CMAKE_POSITION_INDEPENDENT_CODE ON)
9
-
10
- if(POLICY CMP0063)
11
- cmake_policy(SET CMP0063 NEW)
12
- endif()
13
-
14
- if(NOT CMAKE_BUILD_TYPE)
15
- set(CMAKE_BUILD_TYPE Release)
16
- endif(NOT CMAKE_BUILD_TYPE)
17
-
18
- include_directories(extern/milo)
19
-
20
- set(REQUIRED_SUBMODULES
21
- "extern/pybind11/CMakeLists.txt"
22
- "extern/rapidjson/include"
23
- )
24
-
25
- foreach(REQUIRED_SUBMODULE ${REQUIRED_SUBMODULES})
26
- if(NOT EXISTS "${PROJECT_SOURCE_DIR}/${REQUIRED_SUBMODULE}")
27
- # update submodule
28
- # https://cliutils.gitlab.io/modern-cmake/chapters/projects/submodule.html
29
- find_package(Git QUIET)
30
- if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
31
- # Update submodules as needed
32
- option(GIT_SUBMODULE "Check submodules during build" ON)
33
- if(GIT_SUBMODULE)
34
- message(STATUS "Submodule update, this may take some time...")
35
- execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
36
- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
37
- RESULT_VARIABLE GIT_SUBMOD_RESULT)
38
- if(NOT GIT_SUBMOD_RESULT EQUAL "0")
39
- message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
40
- endif()
41
- endif()
42
- endif()
43
- break()
44
- else()
45
- message(STATUS "Found Submodule: ${REQUIRED_SUBMODULE}")
46
- endif()
47
- endforeach()
48
-
49
- foreach(REQUIRED_SUBMODULE ${REQUIRED_SUBMODULES})
50
- if(NOT EXISTS "${PROJECT_SOURCE_DIR}/${REQUIRED_SUBMODULE}")
51
- message(FATAL_ERROR "The submodule ${REQUIRED_SUBMODULE} was not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.")
52
- endif()
53
- endforeach()
54
-
55
- add_subdirectory(extern/pybind11)
56
- include_directories(extern/rapidjson/include)
57
-
58
- add_subdirectory(src)
59
-
60
- # Tests
61
- find_package(GTest)
62
- if(GTEST_FOUND)
63
- enable_testing()
64
- add_subdirectory(tests)
65
- endif()
66
-
67
- if (${CMAKE_BUILD_TYPE} STREQUAL Debug)
68
- add_subdirectory(tools/debug)
69
- endif()
70
-
71
- pybind11_add_module(${PROJECT_NAME} MODULE src/cityflow.cpp)
72
- target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_LIB_NAME})
73
- if(VERSION)
74
- target_compile_definitions(${PROJECT_NAME} PRIVATE -DVERSION=${VERSION})
75
- endif()
76
-
 
1
+ cmake_minimum_required(VERSION 3.5)
2
+ project(cityflow)
3
+
4
+ set(CMAKE_CXX_STANDARD "11" CACHE STRING "")
5
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -DRAPIDJSON_HAS_STDSTRING=1")
6
+ set(CMAKE_CXX_FLAGS_RELEASE "-O2")
7
+ set(CMAKE_CXX_FLAGS_DEBUG "-g")
8
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
9
+
10
+ if(POLICY CMP0063)
11
+ cmake_policy(SET CMP0063 NEW)
12
+ endif()
13
+
14
+ if(NOT CMAKE_BUILD_TYPE)
15
+ set(CMAKE_BUILD_TYPE Release)
16
+ endif(NOT CMAKE_BUILD_TYPE)
17
+
18
+ include_directories(extern/milo)
19
+
20
+ set(REQUIRED_SUBMODULES
21
+ "extern/pybind11/CMakeLists.txt"
22
+ "extern/rapidjson/include"
23
+ )
24
+
25
+ foreach(REQUIRED_SUBMODULE ${REQUIRED_SUBMODULES})
26
+ if(NOT EXISTS "${PROJECT_SOURCE_DIR}/${REQUIRED_SUBMODULE}")
27
+ # update submodule
28
+ # https://cliutils.gitlab.io/modern-cmake/chapters/projects/submodule.html
29
+ find_package(Git QUIET)
30
+ if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
31
+ # Update submodules as needed
32
+ option(GIT_SUBMODULE "Check submodules during build" ON)
33
+ if(GIT_SUBMODULE)
34
+ message(STATUS "Submodule update, this may take some time...")
35
+ execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
36
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
37
+ RESULT_VARIABLE GIT_SUBMOD_RESULT)
38
+ if(NOT GIT_SUBMOD_RESULT EQUAL "0")
39
+ message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
40
+ endif()
41
+ endif()
42
+ endif()
43
+ break()
44
+ else()
45
+ message(STATUS "Found Submodule: ${REQUIRED_SUBMODULE}")
46
+ endif()
47
+ endforeach()
48
+
49
+ foreach(REQUIRED_SUBMODULE ${REQUIRED_SUBMODULES})
50
+ if(NOT EXISTS "${PROJECT_SOURCE_DIR}/${REQUIRED_SUBMODULE}")
51
+ message(FATAL_ERROR "The submodule ${REQUIRED_SUBMODULE} was not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.")
52
+ endif()
53
+ endforeach()
54
+
55
+ add_subdirectory(extern/pybind11)
56
+ include_directories(extern/rapidjson/include)
57
+
58
+ add_subdirectory(src)
59
+
60
+ # Tests
61
+ find_package(GTest)
62
+ if(GTEST_FOUND)
63
+ enable_testing()
64
+ add_subdirectory(tests)
65
+ endif()
66
+
67
+ if (${CMAKE_BUILD_TYPE} STREQUAL Debug)
68
+ add_subdirectory(tools/debug)
69
+ endif()
70
+
71
+ pybind11_add_module(${PROJECT_NAME} MODULE src/cityflow.cpp)
72
+ target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_LIB_NAME})
73
+ if(VERSION)
74
+ target_compile_definitions(${PROJECT_NAME} PRIVATE -DVERSION=${VERSION})
75
+ endif()
76
+
third_party/CityFlow/Dockerfile CHANGED
@@ -1,21 +1,21 @@
1
- FROM ubuntu:16.04
2
-
3
- # c++ dependencies
4
- RUN apt update && \
5
- apt-get install -y build-essential cmake wget git
6
-
7
- # install Miniconda Python 3.6
8
- ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
9
- ENV PATH /opt/conda/bin:$PATH
10
-
11
- RUN wget -P /tmp/ https://repo.continuum.io/miniconda/Miniconda3-4.5.4-Linux-x86_64.sh && \
12
- /bin/bash /tmp/Miniconda3-4.5.4-Linux-x86_64.sh -b -p /opt/conda && \
13
- rm /tmp/Miniconda3-4.5.4-Linux-x86_64.sh && \
14
- ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \
15
- echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc
16
-
17
- # install cityflow
18
- COPY . /home/cityflow
19
- RUN pip install flask && \
20
- cd /home/cityflow && \
21
  pip install .
 
1
+ FROM ubuntu:16.04
2
+
3
+ # c++ dependencies
4
+ RUN apt update && \
5
+ apt-get install -y build-essential cmake wget git
6
+
7
+ # install Miniconda Python 3.6
8
+ ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
9
+ ENV PATH /opt/conda/bin:$PATH
10
+
11
+ RUN wget -P /tmp/ https://repo.continuum.io/miniconda/Miniconda3-4.5.4-Linux-x86_64.sh && \
12
+ /bin/bash /tmp/Miniconda3-4.5.4-Linux-x86_64.sh -b -p /opt/conda && \
13
+ rm /tmp/Miniconda3-4.5.4-Linux-x86_64.sh && \
14
+ ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \
15
+ echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc
16
+
17
+ # install cityflow
18
+ COPY . /home/cityflow
19
+ RUN pip install flask && \
20
+ cd /home/cityflow && \
21
  pip install .
third_party/CityFlow/LICENSE.txt CHANGED
@@ -1,201 +1,201 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- http://www.apache.org/licenses/
4
-
5
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
-
7
- 1. Definitions.
8
-
9
- "License" shall mean the terms and conditions for use, reproduction,
10
- and distribution as defined by Sections 1 through 9 of this document.
11
-
12
- "Licensor" shall mean the copyright owner or entity authorized by
13
- the copyright owner that is granting the License.
14
-
15
- "Legal Entity" shall mean the union of the acting entity and all
16
- other entities that control, are controlled by, or are under common
17
- control with that entity. For the purposes of this definition,
18
- "control" means (i) the power, direct or indirect, to cause the
19
- direction or management of such entity, whether by contract or
20
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
- outstanding shares, or (iii) beneficial ownership of such entity.
22
-
23
- "You" (or "Your") shall mean an individual or Legal Entity
24
- exercising permissions granted by this License.
25
-
26
- "Source" form shall mean the preferred form for making modifications,
27
- including but not limited to software source code, documentation
28
- source, and configuration files.
29
-
30
- "Object" form shall mean any form resulting from mechanical
31
- transformation or translation of a Source form, including but
32
- not limited to compiled object code, generated documentation,
33
- and conversions to other media types.
34
-
35
- "Work" shall mean the work of authorship, whether in Source or
36
- Object form, made available under the License, as indicated by a
37
- copyright notice that is included in or attached to the work
38
- (an example is provided in the Appendix below).
39
-
40
- "Derivative Works" shall mean any work, whether in Source or Object
41
- form, that is based on (or derived from) the Work and for which the
42
- editorial revisions, annotations, elaborations, or other modifications
43
- represent, as a whole, an original work of authorship. For the purposes
44
- of this License, Derivative Works shall not include works that remain
45
- separable from, or merely link (or bind by name) to the interfaces of,
46
- the Work and Derivative Works thereof.
47
-
48
- "Contribution" shall mean any work of authorship, including
49
- the original version of the Work and any modifications or additions
50
- to that Work or Derivative Works thereof, that is intentionally
51
- submitted to Licensor for inclusion in the Work by the copyright owner
52
- or by an individual or Legal Entity authorized to submit on behalf of
53
- the copyright owner. For the purposes of this definition, "submitted"
54
- means any form of electronic, verbal, or written communication sent
55
- to the Licensor or its representatives, including but not limited to
56
- communication on electronic mailing lists, source code control systems,
57
- and issue tracking systems that are managed by, or on behalf of, the
58
- Licensor for the purpose of discussing and improving the Work, but
59
- excluding communication that is conspicuously marked or otherwise
60
- designated in writing by the copyright owner as "Not a Contribution."
61
-
62
- "Contributor" shall mean Licensor and any individual or Legal Entity
63
- on behalf of whom a Contribution has been received by Licensor and
64
- subsequently incorporated within the Work.
65
-
66
- 2. Grant of Copyright License. Subject to the terms and conditions of
67
- this License, each Contributor hereby grants to You a perpetual,
68
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
- copyright license to reproduce, prepare Derivative Works of,
70
- publicly display, publicly perform, sublicense, and distribute the
71
- Work and such Derivative Works in Source or Object form.
72
-
73
- 3. Grant of Patent License. Subject to the terms and conditions of
74
- this License, each Contributor hereby grants to You a perpetual,
75
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
- (except as stated in this section) patent license to make, have made,
77
- use, offer to sell, sell, import, and otherwise transfer the Work,
78
- where such license applies only to those patent claims licensable
79
- by such Contributor that are necessarily infringed by their
80
- Contribution(s) alone or by combination of their Contribution(s)
81
- with the Work to which such Contribution(s) was submitted. If You
82
- institute patent litigation against any entity (including a
83
- cross-claim or counterclaim in a lawsuit) alleging that the Work
84
- or a Contribution incorporated within the Work constitutes direct
85
- or contributory patent infringement, then any patent licenses
86
- granted to You under this License for that Work shall terminate
87
- as of the date such litigation is filed.
88
-
89
- 4. Redistribution. You may reproduce and distribute copies of the
90
- Work or Derivative Works thereof in any medium, with or without
91
- modifications, and in Source or Object form, provided that You
92
- meet the following conditions:
93
-
94
- (a) You must give any other recipients of the Work or
95
- Derivative Works a copy of this License; and
96
-
97
- (b) You must cause any modified files to carry prominent notices
98
- stating that You changed the files; and
99
-
100
- (c) You must retain, in the Source form of any Derivative Works
101
- that You distribute, all copyright, patent, trademark, and
102
- attribution notices from the Source form of the Work,
103
- excluding those notices that do not pertain to any part of
104
- the Derivative Works; and
105
-
106
- (d) If the Work includes a "NOTICE" text file as part of its
107
- distribution, then any Derivative Works that You distribute must
108
- include a readable copy of the attribution notices contained
109
- within such NOTICE file, excluding those notices that do not
110
- pertain to any part of the Derivative Works, in at least one
111
- of the following places: within a NOTICE text file distributed
112
- as part of the Derivative Works; within the Source form or
113
- documentation, if provided along with the Derivative Works; or,
114
- within a display generated by the Derivative Works, if and
115
- wherever such third-party notices normally appear. The contents
116
- of the NOTICE file are for informational purposes only and
117
- do not modify the License. You may add Your own attribution
118
- notices within Derivative Works that You distribute, alongside
119
- or as an addendum to the NOTICE text from the Work, provided
120
- that such additional attribution notices cannot be construed
121
- as modifying the License.
122
-
123
- You may add Your own copyright statement to Your modifications and
124
- may provide additional or different license terms and conditions
125
- for use, reproduction, or distribution of Your modifications, or
126
- for any such Derivative Works as a whole, provided Your use,
127
- reproduction, and distribution of the Work otherwise complies with
128
- the conditions stated in this License.
129
-
130
- 5. Submission of Contributions. Unless You explicitly state otherwise,
131
- any Contribution intentionally submitted for inclusion in the Work
132
- by You to the Licensor shall be under the terms and conditions of
133
- this License, without any additional terms or conditions.
134
- Notwithstanding the above, nothing herein shall supersede or modify
135
- the terms of any separate license agreement you may have executed
136
- with Licensor regarding such Contributions.
137
-
138
- 6. Trademarks. This License does not grant permission to use the trade
139
- names, trademarks, service marks, or product names of the Licensor,
140
- except as required for reasonable and customary use in describing the
141
- origin of the Work and reproducing the content of the NOTICE file.
142
-
143
- 7. Disclaimer of Warranty. Unless required by applicable law or
144
- agreed to in writing, Licensor provides the Work (and each
145
- Contributor provides its Contributions) on an "AS IS" BASIS,
146
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
- implied, including, without limitation, any warranties or conditions
148
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
- PARTICULAR PURPOSE. You are solely responsible for determining the
150
- appropriateness of using or redistributing the Work and assume any
151
- risks associated with Your exercise of permissions under this License.
152
-
153
- 8. Limitation of Liability. In no event and under no legal theory,
154
- whether in tort (including negligence), contract, or otherwise,
155
- unless required by applicable law (such as deliberate and grossly
156
- negligent acts) or agreed to in writing, shall any Contributor be
157
- liable to You for damages, including any direct, indirect, special,
158
- incidental, or consequential damages of any character arising as a
159
- result of this License or out of the use or inability to use the
160
- Work (including but not limited to damages for loss of goodwill,
161
- work stoppage, computer failure or malfunction, or any and all
162
- other commercial damages or losses), even if such Contributor
163
- has been advised of the possibility of such damages.
164
-
165
- 9. Accepting Warranty or Additional Liability. While redistributing
166
- the Work or Derivative Works thereof, You may choose to offer,
167
- and charge a fee for, acceptance of support, warranty, indemnity,
168
- or other liability obligations and/or rights consistent with this
169
- License. However, in accepting such obligations, You may act only
170
- on Your own behalf and on Your sole responsibility, not on behalf
171
- of any other Contributor, and only if You agree to indemnify,
172
- defend, and hold each Contributor harmless for any liability
173
- incurred by, or claims asserted against, such Contributor by reason
174
- of your accepting any such warranty or additional liability.
175
-
176
- END OF TERMS AND CONDITIONS
177
-
178
- APPENDIX: How to apply the Apache License to your work.
179
-
180
- To apply the Apache License to your work, attach the following
181
- boilerplate notice, with the fields enclosed by brackets "[]"
182
- replaced with your own identifying information. (Don't include
183
- the brackets!) The text should be enclosed in the appropriate
184
- comment syntax for the file format. We also recommend that a
185
- file or class name and description of purpose be included on the
186
- same "printed page" as the copyright notice for easier
187
- identification within third-party archives.
188
-
189
- Copyright [yyyy] [name of copyright owner]
190
-
191
- Licensed under the Apache License, Version 2.0 (the "License");
192
- you may not use this file except in compliance with the License.
193
- You may obtain a copy of the License at
194
-
195
- http://www.apache.org/licenses/LICENSE-2.0
196
-
197
- Unless required by applicable law or agreed to in writing, software
198
- distributed under the License is distributed on an "AS IS" BASIS,
199
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
- See the License for the specific language governing permissions and
201
  limitations under the License.
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
  limitations under the License.
third_party/CityFlow/README.rst CHANGED
@@ -1,51 +1,51 @@
1
- CityFlow
2
- ============
3
-
4
- .. image:: https://readthedocs.org/projects/cityflow/badge/?version=latest
5
- :target: https://cityflow.readthedocs.io/en/latest/?badge=latest
6
- :alt: Documentation Status
7
-
8
- .. image:: https://dev.azure.com/CityFlow/CityFlow/_apis/build/status/cityflow-project.CityFlow?branchName=master
9
- :target: https://dev.azure.com/CityFlow/CityFlow/_build/latest?definitionId=2&branchName=master
10
- :alt: Build Status
11
-
12
- CityFlow is a multi-agent reinforcement learning environment for large-scale city traffic scenario.
13
-
14
- Checkout these features!
15
-
16
- - A microscopic traffic simulator which simulates the behavior of each vehicle, providing highest level detail of traffic evolution.
17
- - Supports flexible definitions for road network and traffic flow
18
- - Provides friendly python interface for reinforcement learning
19
- - **Fast!** Elaborately designed data structure and simulation algorithm with multithreading. Capable of simulating city-wide traffic. See the performance comparison with SUMO [#sumo]_.
20
-
21
- .. figure:: https://user-images.githubusercontent.com/44251346/54403537-5ce16b00-470b-11e9-928d-76c8ba0ab463.png
22
- :align: center
23
- :alt: performance compared with SUMO
24
-
25
- Performance comparison between CityFlow with different number of threads (1, 2, 4, 8) and SUMO. From small 1x1 grid roadnet to city-level 30x30 roadnet. Even faster when you need to interact with the simulator through python API.
26
-
27
- Screencast
28
- ----------
29
-
30
- .. figure:: https://user-images.githubusercontent.com/44251346/62375390-c9e98600-b570-11e9-8808-e13dbe776f1e.gif
31
- :align: center
32
- :alt: demo
33
-
34
- Featured Research and Projects Using CityFlow
35
- ---------------------------------------------
36
- - `PressLight: Learning Max Pressure Control to Coordinate Traffic Signals in Arterial Network (KDD 2019) <http://personal.psu.edu/hzw77/publications/presslight-kdd19.pdf>`_
37
- - `CoLight: Learning Network-level Cooperation for Traffic Signal Control <https://arxiv.org/abs/1905.05717>`_
38
- - `Traffic Signal Control Benchmark <https://traffic-signal-control.github.io/>`_
39
- - `TSCC2050: A Traffic Signal Control Game by Tianrang Intelligence (in Chinese) <http://game.tscc2050.com/>`_ [#tianrang]_
40
-
41
- Links
42
- -----
43
-
44
- - `WWW 2019 Demo Paper <https://arxiv.org/abs/1905.05217>`_
45
- - `Home Page <http://cityflow-project.github.io/>`_
46
- - `Documentation and Quick Start <https://cityflow.readthedocs.io/en/latest/>`_
47
- - `Docker <https://hub.docker.com/r/cityflowproject/cityflow>`_
48
-
49
-
50
- .. [#sumo] `SUMO home page <https://sumo.dlr.de/index.html>`_
51
- .. [#tianrang] `Tianrang Intelligence home page <https://www.tianrang.com/>`_
 
1
+ CityFlow
2
+ ============
3
+
4
+ .. image:: https://readthedocs.org/projects/cityflow/badge/?version=latest
5
+ :target: https://cityflow.readthedocs.io/en/latest/?badge=latest
6
+ :alt: Documentation Status
7
+
8
+ .. image:: https://dev.azure.com/CityFlow/CityFlow/_apis/build/status/cityflow-project.CityFlow?branchName=master
9
+ :target: https://dev.azure.com/CityFlow/CityFlow/_build/latest?definitionId=2&branchName=master
10
+ :alt: Build Status
11
+
12
+ CityFlow is a multi-agent reinforcement learning environment for large-scale city traffic scenario.
13
+
14
+ Checkout these features!
15
+
16
+ - A microscopic traffic simulator which simulates the behavior of each vehicle, providing highest level detail of traffic evolution.
17
+ - Supports flexible definitions for road network and traffic flow
18
+ - Provides friendly python interface for reinforcement learning
19
+ - **Fast!** Elaborately designed data structure and simulation algorithm with multithreading. Capable of simulating city-wide traffic. See the performance comparison with SUMO [#sumo]_.
20
+
21
+ .. figure:: https://user-images.githubusercontent.com/44251346/54403537-5ce16b00-470b-11e9-928d-76c8ba0ab463.png
22
+ :align: center
23
+ :alt: performance compared with SUMO
24
+
25
+ Performance comparison between CityFlow with different number of threads (1, 2, 4, 8) and SUMO. From small 1x1 grid roadnet to city-level 30x30 roadnet. Even faster when you need to interact with the simulator through python API.
26
+
27
+ Screencast
28
+ ----------
29
+
30
+ .. figure:: https://user-images.githubusercontent.com/44251346/62375390-c9e98600-b570-11e9-8808-e13dbe776f1e.gif
31
+ :align: center
32
+ :alt: demo
33
+
34
+ Featured Research and Projects Using CityFlow
35
+ ---------------------------------------------
36
+ - `PressLight: Learning Max Pressure Control to Coordinate Traffic Signals in Arterial Network (KDD 2019) <http://personal.psu.edu/hzw77/publications/presslight-kdd19.pdf>`_
37
+ - `CoLight: Learning Network-level Cooperation for Traffic Signal Control <https://arxiv.org/abs/1905.05717>`_
38
+ - `Traffic Signal Control Benchmark <https://traffic-signal-control.github.io/>`_
39
+ - `TSCC2050: A Traffic Signal Control Game by Tianrang Intelligence (in Chinese) <http://game.tscc2050.com/>`_ [#tianrang]_
40
+
41
+ Links
42
+ -----
43
+
44
+ - `WWW 2019 Demo Paper <https://arxiv.org/abs/1905.05217>`_
45
+ - `Home Page <http://cityflow-project.github.io/>`_
46
+ - `Documentation and Quick Start <https://cityflow.readthedocs.io/en/latest/>`_
47
+ - `Docker <https://hub.docker.com/r/cityflowproject/cityflow>`_
48
+
49
+
50
+ .. [#sumo] `SUMO home page <https://sumo.dlr.de/index.html>`_
51
+ .. [#tianrang] `Tianrang Intelligence home page <https://www.tianrang.com/>`_
third_party/CityFlow/docs/Makefile CHANGED
@@ -1,20 +1,20 @@
1
- # Minimal makefile for Sphinx documentation
2
- #
3
-
4
- # You can set these variables from the command line.
5
- SPHINXOPTS =
6
- SPHINXBUILD = sphinx-build
7
- SPHINXPROJ = CityFlow
8
- SOURCEDIR = source
9
- BUILDDIR = build
10
-
11
- # Put it first so that "make" without argument is like "make help".
12
- help:
13
- @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14
-
15
- .PHONY: help Makefile
16
-
17
- # Catch-all target: route all unknown targets to Sphinx using the new
18
- # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19
- %: Makefile
20
  @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
 
1
+ # Minimal makefile for Sphinx documentation
2
+ #
3
+
4
+ # You can set these variables from the command line.
5
+ SPHINXOPTS =
6
+ SPHINXBUILD = sphinx-build
7
+ SPHINXPROJ = CityFlow
8
+ SOURCEDIR = source
9
+ BUILDDIR = build
10
+
11
+ # Put it first so that "make" without argument is like "make help".
12
+ help:
13
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14
+
15
+ .PHONY: help Makefile
16
+
17
+ # Catch-all target: route all unknown targets to Sphinx using the new
18
+ # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19
+ %: Makefile
20
  @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
third_party/CityFlow/docs/make.bat CHANGED
@@ -1,36 +1,36 @@
1
- @ECHO OFF
2
-
3
- pushd %~dp0
4
-
5
- REM Command file for Sphinx documentation
6
-
7
- if "%SPHINXBUILD%" == "" (
8
- set SPHINXBUILD=sphinx-build
9
- )
10
- set SOURCEDIR=source
11
- set BUILDDIR=build
12
- set SPHINXPROJ=CityFlow
13
-
14
- if "%1" == "" goto help
15
-
16
- %SPHINXBUILD% >NUL 2>NUL
17
- if errorlevel 9009 (
18
- echo.
19
- echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
20
- echo.installed, then set the SPHINXBUILD environment variable to point
21
- echo.to the full path of the 'sphinx-build' executable. Alternatively you
22
- echo.may add the Sphinx directory to PATH.
23
- echo.
24
- echo.If you don't have Sphinx installed, grab it from
25
- echo.http://sphinx-doc.org/
26
- exit /b 1
27
- )
28
-
29
- %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30
- goto end
31
-
32
- :help
33
- %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34
-
35
- :end
36
- popd
 
1
+ @ECHO OFF
2
+
3
+ pushd %~dp0
4
+
5
+ REM Command file for Sphinx documentation
6
+
7
+ if "%SPHINXBUILD%" == "" (
8
+ set SPHINXBUILD=sphinx-build
9
+ )
10
+ set SOURCEDIR=source
11
+ set BUILDDIR=build
12
+ set SPHINXPROJ=CityFlow
13
+
14
+ if "%1" == "" goto help
15
+
16
+ %SPHINXBUILD% >NUL 2>NUL
17
+ if errorlevel 9009 (
18
+ echo.
19
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
20
+ echo.installed, then set the SPHINXBUILD environment variable to point
21
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
22
+ echo.may add the Sphinx directory to PATH.
23
+ echo.
24
+ echo.If you don't have Sphinx installed, grab it from
25
+ echo.http://sphinx-doc.org/
26
+ exit /b 1
27
+ )
28
+
29
+ %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30
+ goto end
31
+
32
+ :help
33
+ %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34
+
35
+ :end
36
+ popd
third_party/CityFlow/docs/source/conf.py CHANGED
@@ -1,155 +1,155 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Configuration file for the Sphinx documentation builder.
4
- #
5
- # This file does only contain a selection of the most common options. For a
6
- # full list see the documentation:
7
- # http://www.sphinx-doc.org/en/master/config
8
-
9
- # -- Path setup --------------------------------------------------------------
10
-
11
- # If extensions (or modules to document with autodoc) are in another directory,
12
- # add these directories to sys.path here. If the directory is relative to the
13
- # documentation root, use os.path.abspath to make it absolute, like shown here.
14
- #
15
- # import os
16
- # import sys
17
- # sys.path.insert(0, os.path.abspath('.'))
18
-
19
-
20
- # -- Project information -----------------------------------------------------
21
-
22
- project = 'CityFlow'
23
- copyright = '2019, CityFlow'
24
- author = 'Huichu Zhang'
25
-
26
- # The short X.Y version
27
- version = ''
28
- # The full version, including alpha/beta/rc tags
29
- release = '0.1'
30
-
31
-
32
- # -- General configuration ---------------------------------------------------
33
-
34
- # If your documentation needs a minimal Sphinx version, state it here.
35
- #
36
- # needs_sphinx = '1.0'
37
-
38
- # Add any Sphinx extension module names here, as strings. They can be
39
- # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
40
- # ones.
41
- extensions = [
42
- ]
43
-
44
- # Add any paths that contain templates here, relative to this directory.
45
- templates_path = ['_templates']
46
-
47
- # The suffix(es) of source filenames.
48
- # You can specify multiple suffix as a list of string:
49
- #
50
- # source_suffix = ['.rst', '.md']
51
- source_suffix = '.rst'
52
-
53
- # The master toctree document.
54
- master_doc = 'index'
55
-
56
- # The language for content autogenerated by Sphinx. Refer to documentation
57
- # for a list of supported languages.
58
- #
59
- # This is also used if you do content translation via gettext catalogs.
60
- # Usually you set "language" from the command line for these cases.
61
- language = None
62
-
63
- # List of patterns, relative to source directory, that match files and
64
- # directories to ignore when looking for source files.
65
- # This pattern also affects html_static_path and html_extra_path .
66
- exclude_patterns = []
67
-
68
- # The name of the Pygments (syntax highlighting) style to use.
69
- pygments_style = 'sphinx'
70
-
71
-
72
- # -- Options for HTML output -------------------------------------------------
73
-
74
- # The theme to use for HTML and HTML Help pages. See the documentation for
75
- # a list of builtin themes.
76
- #
77
- html_theme = 'sphinx_rtd_theme'
78
-
79
- # Theme options are theme-specific and customize the look and feel of a theme
80
- # further. For a list of options available for each theme, see the
81
- # documentation.
82
- #
83
- # html_theme_options = {}
84
-
85
- # Add any paths that contain custom static files (such as style sheets) here,
86
- # relative to this directory. They are copied after the builtin static files,
87
- # so a file named "default.css" will overwrite the builtin "default.css".
88
- html_static_path = ['_static']
89
-
90
- # Custom sidebar templates, must be a dictionary that maps document names
91
- # to template names.
92
- #
93
- # The default sidebars (for documents that don't match any pattern) are
94
- # defined by theme itself. Builtin themes are using these templates by
95
- # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
96
- # 'searchbox.html']``.
97
- #
98
- # html_sidebars = {}
99
-
100
-
101
- # -- Options for HTMLHelp output ---------------------------------------------
102
-
103
- # Output file base name for HTML help builder.
104
- htmlhelp_basename = 'CityFlowdoc'
105
-
106
-
107
- # -- Options for LaTeX output ------------------------------------------------
108
-
109
- latex_elements = {
110
- # The paper size ('letterpaper' or 'a4paper').
111
- #
112
- # 'papersize': 'letterpaper',
113
-
114
- # The font size ('10pt', '11pt' or '12pt').
115
- #
116
- # 'pointsize': '10pt',
117
-
118
- # Additional stuff for the LaTeX preamble.
119
- #
120
- # 'preamble': '',
121
-
122
- # Latex figure (float) alignment
123
- #
124
- # 'figure_align': 'htbp',
125
- }
126
-
127
- # Grouping the document tree into LaTeX files. List of tuples
128
- # (source start file, target name, title,
129
- # author, documentclass [howto, manual, or own class]).
130
- latex_documents = [
131
- (master_doc, 'CityFlow.tex', 'CityFlow Documentation',
132
- 'Huichu Zhang', 'manual'),
133
- ]
134
-
135
-
136
- # -- Options for manual page output ------------------------------------------
137
-
138
- # One entry per manual page. List of tuples
139
- # (source start file, name, description, authors, manual section).
140
- man_pages = [
141
- (master_doc, 'cityflow', 'CityFlow Documentation',
142
- [author], 1)
143
- ]
144
-
145
-
146
- # -- Options for Texinfo output ----------------------------------------------
147
-
148
- # Grouping the document tree into Texinfo files. List of tuples
149
- # (source start file, target name, title, author,
150
- # dir menu entry, description, category)
151
- texinfo_documents = [
152
- (master_doc, 'CityFlow', 'CityFlow Documentation',
153
- author, 'CityFlow', 'One line description of project.',
154
- 'Miscellaneous'),
155
  ]
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Configuration file for the Sphinx documentation builder.
4
+ #
5
+ # This file does only contain a selection of the most common options. For a
6
+ # full list see the documentation:
7
+ # http://www.sphinx-doc.org/en/master/config
8
+
9
+ # -- Path setup --------------------------------------------------------------
10
+
11
+ # If extensions (or modules to document with autodoc) are in another directory,
12
+ # add these directories to sys.path here. If the directory is relative to the
13
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
14
+ #
15
+ # import os
16
+ # import sys
17
+ # sys.path.insert(0, os.path.abspath('.'))
18
+
19
+
20
+ # -- Project information -----------------------------------------------------
21
+
22
+ project = 'CityFlow'
23
+ copyright = '2019, CityFlow'
24
+ author = 'Huichu Zhang'
25
+
26
+ # The short X.Y version
27
+ version = ''
28
+ # The full version, including alpha/beta/rc tags
29
+ release = '0.1'
30
+
31
+
32
+ # -- General configuration ---------------------------------------------------
33
+
34
+ # If your documentation needs a minimal Sphinx version, state it here.
35
+ #
36
+ # needs_sphinx = '1.0'
37
+
38
+ # Add any Sphinx extension module names here, as strings. They can be
39
+ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
40
+ # ones.
41
+ extensions = [
42
+ ]
43
+
44
+ # Add any paths that contain templates here, relative to this directory.
45
+ templates_path = ['_templates']
46
+
47
+ # The suffix(es) of source filenames.
48
+ # You can specify multiple suffix as a list of string:
49
+ #
50
+ # source_suffix = ['.rst', '.md']
51
+ source_suffix = '.rst'
52
+
53
+ # The master toctree document.
54
+ master_doc = 'index'
55
+
56
+ # The language for content autogenerated by Sphinx. Refer to documentation
57
+ # for a list of supported languages.
58
+ #
59
+ # This is also used if you do content translation via gettext catalogs.
60
+ # Usually you set "language" from the command line for these cases.
61
+ language = None
62
+
63
+ # List of patterns, relative to source directory, that match files and
64
+ # directories to ignore when looking for source files.
65
+ # This pattern also affects html_static_path and html_extra_path .
66
+ exclude_patterns = []
67
+
68
+ # The name of the Pygments (syntax highlighting) style to use.
69
+ pygments_style = 'sphinx'
70
+
71
+
72
+ # -- Options for HTML output -------------------------------------------------
73
+
74
+ # The theme to use for HTML and HTML Help pages. See the documentation for
75
+ # a list of builtin themes.
76
+ #
77
+ html_theme = 'sphinx_rtd_theme'
78
+
79
+ # Theme options are theme-specific and customize the look and feel of a theme
80
+ # further. For a list of options available for each theme, see the
81
+ # documentation.
82
+ #
83
+ # html_theme_options = {}
84
+
85
+ # Add any paths that contain custom static files (such as style sheets) here,
86
+ # relative to this directory. They are copied after the builtin static files,
87
+ # so a file named "default.css" will overwrite the builtin "default.css".
88
+ html_static_path = ['_static']
89
+
90
+ # Custom sidebar templates, must be a dictionary that maps document names
91
+ # to template names.
92
+ #
93
+ # The default sidebars (for documents that don't match any pattern) are
94
+ # defined by theme itself. Builtin themes are using these templates by
95
+ # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
96
+ # 'searchbox.html']``.
97
+ #
98
+ # html_sidebars = {}
99
+
100
+
101
+ # -- Options for HTMLHelp output ---------------------------------------------
102
+
103
+ # Output file base name for HTML help builder.
104
+ htmlhelp_basename = 'CityFlowdoc'
105
+
106
+
107
+ # -- Options for LaTeX output ------------------------------------------------
108
+
109
+ latex_elements = {
110
+ # The paper size ('letterpaper' or 'a4paper').
111
+ #
112
+ # 'papersize': 'letterpaper',
113
+
114
+ # The font size ('10pt', '11pt' or '12pt').
115
+ #
116
+ # 'pointsize': '10pt',
117
+
118
+ # Additional stuff for the LaTeX preamble.
119
+ #
120
+ # 'preamble': '',
121
+
122
+ # Latex figure (float) alignment
123
+ #
124
+ # 'figure_align': 'htbp',
125
+ }
126
+
127
+ # Grouping the document tree into LaTeX files. List of tuples
128
+ # (source start file, target name, title,
129
+ # author, documentclass [howto, manual, or own class]).
130
+ latex_documents = [
131
+ (master_doc, 'CityFlow.tex', 'CityFlow Documentation',
132
+ 'Huichu Zhang', 'manual'),
133
+ ]
134
+
135
+
136
+ # -- Options for manual page output ------------------------------------------
137
+
138
+ # One entry per manual page. List of tuples
139
+ # (source start file, name, description, authors, manual section).
140
+ man_pages = [
141
+ (master_doc, 'cityflow', 'CityFlow Documentation',
142
+ [author], 1)
143
+ ]
144
+
145
+
146
+ # -- Options for Texinfo output ----------------------------------------------
147
+
148
+ # Grouping the document tree into Texinfo files. List of tuples
149
+ # (source start file, target name, title, author,
150
+ # dir menu entry, description, category)
151
+ texinfo_documents = [
152
+ (master_doc, 'CityFlow', 'CityFlow Documentation',
153
+ author, 'CityFlow', 'One line description of project.',
154
+ 'Miscellaneous'),
155
  ]
third_party/CityFlow/docs/source/flow.rst CHANGED
@@ -1,23 +1,23 @@
1
- .. _flow:
2
-
3
- Flow File Format
4
- ===================
5
-
6
- Flow file defines the traffic flow. Each flow contains following field:
7
-
8
- - ``vehicle``: defines the parameter of vehicle.
9
- - length: length of the vehicle
10
- - width: width of the vehicle
11
- - maxPosAcc: maximum acceleration (in m/s)
12
- - maxNegAcc: maximum deceleration (in m/s)
13
- - usualPosAcc: usual acceleration (in m/s)
14
- - usualNegAcc: usual deceleration (in m/s)
15
- - minGap: minimum acceptable gap with leading vehicle (in meter)
16
- - maxSpeed: maximum cruising speed (in m/s)
17
- - headwayTime: desired headway time (in seconds) with leading vehicle, keep *current speed \* headwayTime* gap.
18
- - ``route``: defines the route, all vehicles of this flow will follow the route. Specify the source and the destination, optionally some anchor points and the router will connect them with shortest paths automatically.
19
- - ``interval``: defines the interval of consecutive vehicles (in seconds). If the interval is too small, vehicles may not be able to enter the road due to blockage, it will be held and let go once there are enough space.
20
- - ``startTime``, ``endTime``: Flow will generate vehicles between time [startTime, endTime] (in seconds), including ``startTime`` and ``endTime``.
21
-
22
- .. note::
23
- Runnable sample flow files can be found in ``examples`` folder.
 
1
+ .. _flow:
2
+
3
+ Flow File Format
4
+ ===================
5
+
6
+ Flow file defines the traffic flow. Each flow contains following field:
7
+
8
+ - ``vehicle``: defines the parameter of vehicle.
9
+ - length: length of the vehicle
10
+ - width: width of the vehicle
11
+ - maxPosAcc: maximum acceleration (in m/s)
12
+ - maxNegAcc: maximum deceleration (in m/s)
13
+ - usualPosAcc: usual acceleration (in m/s)
14
+ - usualNegAcc: usual deceleration (in m/s)
15
+ - minGap: minimum acceptable gap with leading vehicle (in meter)
16
+ - maxSpeed: maximum cruising speed (in m/s)
17
+ - headwayTime: desired headway time (in seconds) with leading vehicle, keep *current speed \* headwayTime* gap.
18
+ - ``route``: defines the route, all vehicles of this flow will follow the route. Specify the source and the destination, optionally some anchor points and the router will connect them with shortest paths automatically.
19
+ - ``interval``: defines the interval of consecutive vehicles (in seconds). If the interval is too small, vehicles may not be able to enter the road due to blockage, it will be held and let go once there are enough space.
20
+ - ``startTime``, ``endTime``: Flow will generate vehicles between time [startTime, endTime] (in seconds), including ``startTime`` and ``endTime``.
21
+
22
+ .. note::
23
+ Runnable sample flow files can be found in ``examples`` folder.
third_party/CityFlow/docs/source/index.rst CHANGED
@@ -1,18 +1,18 @@
1
- .. CityFlow documentation master file, created by
2
- sphinx-quickstart on Wed Mar 13 21:59:27 2019.
3
- You can adapt this file completely to your liking, but it should at least
4
- contain the root `toctree` directive.
5
-
6
- Welcome to CityFlow's documentation!
7
- ====================================
8
-
9
- .. toctree::
10
- :maxdepth: 2
11
- :caption: Contents:
12
-
13
- introduction
14
- install
15
- start
16
- roadnet
17
- flow
18
- replay
 
1
+ .. CityFlow documentation master file, created by
2
+ sphinx-quickstart on Wed Mar 13 21:59:27 2019.
3
+ You can adapt this file completely to your liking, but it should at least
4
+ contain the root `toctree` directive.
5
+
6
+ Welcome to CityFlow's documentation!
7
+ ====================================
8
+
9
+ .. toctree::
10
+ :maxdepth: 2
11
+ :caption: Contents:
12
+
13
+ introduction
14
+ install
15
+ start
16
+ roadnet
17
+ flow
18
+ replay
third_party/CityFlow/docs/source/install.rst CHANGED
@@ -1,66 +1,66 @@
1
- .. _install:
2
-
3
- Installation Guide
4
- ==================
5
-
6
- Docker
7
- ------
8
-
9
- The easiest way to use CityFlow is via docker.
10
-
11
- .. code-block:: shell
12
-
13
- docker pull cityflowproject/cityflow:latest
14
-
15
- This will create docker image ``cityflow:latest``.
16
-
17
- .. code-block:: shell
18
-
19
- docker run -it cityflowproject/cityflow:latest
20
-
21
- Create and start a container, CityFlow is out-of-the-box along with miniconda with python3.6.
22
-
23
- .. code-block:: python
24
-
25
- import cityflow
26
- eng = cityflow.Engine
27
-
28
- Build From Source
29
- -----------------
30
-
31
- If you want to get nightly version of CityFlow or running on native system, you can build CityFlow from source. Currently, we only support building on Unix systems. This guide is based on Ubuntu 16.04.
32
-
33
- CityFlow has little dependencies, so building from source is not scary.
34
-
35
- 1. Check that you have python 3 installed. Other version of python might work, however, we only tested on python with version >= 3.5.
36
-
37
-
38
- 2. Install cpp dependencies
39
-
40
- .. code-block:: shell
41
-
42
- sudo apt update && sudo apt install -y build-essential cmake
43
-
44
- 3. Clone CityFlow project from github.
45
-
46
- .. code-block:: shell
47
-
48
- git clone https://github.com/cityflow-project/CityFlow.git
49
-
50
- 4. Go to CityFlow project's root directory and run
51
-
52
- .. code-block:: shell
53
-
54
- pip install .
55
-
56
- 5. Wait for installation to complete and CityFlow should be successfully installed.
57
-
58
- .. code-block:: python
59
-
60
- import cityflow
61
- eng = cityflow.Engine
62
-
63
- For Windows Users
64
- ------------------
65
-
66
- For Windows users, it is recommended to run CityFlow under Windows Subsystem for Linux (WSL) or use docker.
 
1
+ .. _install:
2
+
3
+ Installation Guide
4
+ ==================
5
+
6
+ Docker
7
+ ------
8
+
9
+ The easiest way to use CityFlow is via docker.
10
+
11
+ .. code-block:: shell
12
+
13
+ docker pull cityflowproject/cityflow:latest
14
+
15
+ This will create docker image ``cityflow:latest``.
16
+
17
+ .. code-block:: shell
18
+
19
+ docker run -it cityflowproject/cityflow:latest
20
+
21
+ Create and start a container, CityFlow is out-of-the-box along with miniconda with python3.6.
22
+
23
+ .. code-block:: python
24
+
25
+ import cityflow
26
+ eng = cityflow.Engine
27
+
28
+ Build From Source
29
+ -----------------
30
+
31
+ If you want to get nightly version of CityFlow or running on native system, you can build CityFlow from source. Currently, we only support building on Unix systems. This guide is based on Ubuntu 16.04.
32
+
33
+ CityFlow has little dependencies, so building from source is not scary.
34
+
35
+ 1. Check that you have python 3 installed. Other version of python might work, however, we only tested on python with version >= 3.5.
36
+
37
+
38
+ 2. Install cpp dependencies
39
+
40
+ .. code-block:: shell
41
+
42
+ sudo apt update && sudo apt install -y build-essential cmake
43
+
44
+ 3. Clone CityFlow project from github.
45
+
46
+ .. code-block:: shell
47
+
48
+ git clone https://github.com/cityflow-project/CityFlow.git
49
+
50
+ 4. Go to CityFlow project's root directory and run
51
+
52
+ .. code-block:: shell
53
+
54
+ pip install .
55
+
56
+ 5. Wait for installation to complete and CityFlow should be successfully installed.
57
+
58
+ .. code-block:: python
59
+
60
+ import cityflow
61
+ eng = cityflow.Engine
62
+
63
+ For Windows Users
64
+ ------------------
65
+
66
+ For Windows users, it is recommended to run CityFlow under Windows Subsystem for Linux (WSL) or use docker.
third_party/CityFlow/examples/config.json CHANGED
@@ -1,12 +1,12 @@
1
- {
2
- "interval": 1.0,
3
- "seed": 0,
4
- "dir": "CityFlow/examples/",
5
- "roadnetFile": "roadnet.json",
6
- "flowFile": "flow.json",
7
- "rlTrafficLight": false,
8
- "laneChange": false,
9
- "saveReplay": true,
10
- "roadnetLogFile": "replay_roadnet.json",
11
- "replayLogFile": "replay.txt"
12
  }
 
1
+ {
2
+ "interval": 1.0,
3
+ "seed": 0,
4
+ "dir": "CityFlow/examples/",
5
+ "roadnetFile": "roadnet.json",
6
+ "flowFile": "flow.json",
7
+ "rlTrafficLight": false,
8
+ "laneChange": false,
9
+ "saveReplay": true,
10
+ "roadnetLogFile": "replay_roadnet.json",
11
+ "replayLogFile": "replay.txt"
12
  }
third_party/CityFlow/examples/flow.json CHANGED
@@ -1,242 +1,242 @@
1
- [
2
- {
3
- "vehicle": {
4
- "length": 5.0,
5
- "width": 2.0,
6
- "maxPosAcc": 2.0,
7
- "maxNegAcc": 4.5,
8
- "usualPosAcc": 2.0,
9
- "usualNegAcc": 4.5,
10
- "minGap": 2.5,
11
- "maxSpeed": 16.67,
12
- "headwayTime": 1.5
13
- },
14
- "route": [
15
- "road_0_1_0",
16
- "road_1_1_0"
17
- ],
18
- "interval": 5.0,
19
- "startTime": 0,
20
- "endTime": -1
21
- },
22
- {
23
- "vehicle": {
24
- "length": 5.0,
25
- "width": 2.0,
26
- "maxPosAcc": 2.0,
27
- "maxNegAcc": 4.5,
28
- "usualPosAcc": 2.0,
29
- "usualNegAcc": 4.5,
30
- "minGap": 2.5,
31
- "maxSpeed": 16.67,
32
- "headwayTime": 1.5
33
- },
34
- "route": [
35
- "road_2_1_2",
36
- "road_1_1_2"
37
- ],
38
- "interval": 5.0,
39
- "startTime": 0,
40
- "endTime": -1
41
- },
42
- {
43
- "vehicle": {
44
- "length": 5.0,
45
- "width": 2.0,
46
- "maxPosAcc": 2.0,
47
- "maxNegAcc": 4.5,
48
- "usualPosAcc": 2.0,
49
- "usualNegAcc": 4.5,
50
- "minGap": 2.5,
51
- "maxSpeed": 16.67,
52
- "headwayTime": 1.5
53
- },
54
- "route": [
55
- "road_1_0_1",
56
- "road_1_1_1"
57
- ],
58
- "interval": 5.0,
59
- "startTime": 0,
60
- "endTime": -1
61
- },
62
- {
63
- "vehicle": {
64
- "length": 5.0,
65
- "width": 2.0,
66
- "maxPosAcc": 2.0,
67
- "maxNegAcc": 4.5,
68
- "usualPosAcc": 2.0,
69
- "usualNegAcc": 4.5,
70
- "minGap": 2.5,
71
- "maxSpeed": 16.67,
72
- "headwayTime": 1.5
73
- },
74
- "route": [
75
- "road_1_2_3",
76
- "road_1_1_3"
77
- ],
78
- "interval": 5.0,
79
- "startTime": 0,
80
- "endTime": -1
81
- },
82
- {
83
- "vehicle": {
84
- "length": 5.0,
85
- "width": 2.0,
86
- "maxPosAcc": 2.0,
87
- "maxNegAcc": 4.5,
88
- "usualPosAcc": 2.0,
89
- "usualNegAcc": 4.5,
90
- "minGap": 2.5,
91
- "maxSpeed": 16.67,
92
- "headwayTime": 1.5
93
- },
94
- "route": [
95
- "road_1_0_1",
96
- "road_1_1_0"
97
- ],
98
- "interval": 5.0,
99
- "startTime": 0,
100
- "endTime": -1
101
- },
102
- {
103
- "vehicle": {
104
- "length": 5.0,
105
- "width": 2.0,
106
- "maxPosAcc": 2.0,
107
- "maxNegAcc": 4.5,
108
- "usualPosAcc": 2.0,
109
- "usualNegAcc": 4.5,
110
- "minGap": 2.5,
111
- "maxSpeed": 16.67,
112
- "headwayTime": 1.5
113
- },
114
- "route": [
115
- "road_0_1_0",
116
- "road_1_1_1"
117
- ],
118
- "interval": 5.0,
119
- "startTime": 0,
120
- "endTime": -1
121
- },
122
- {
123
- "vehicle": {
124
- "length": 5.0,
125
- "width": 2.0,
126
- "maxPosAcc": 2.0,
127
- "maxNegAcc": 4.5,
128
- "usualPosAcc": 2.0,
129
- "usualNegAcc": 4.5,
130
- "minGap": 2.5,
131
- "maxSpeed": 16.67,
132
- "headwayTime": 1.5
133
- },
134
- "route": [
135
- "road_2_1_2",
136
- "road_1_1_3"
137
- ],
138
- "interval": 5.0,
139
- "startTime": 0,
140
- "endTime": -1
141
- },
142
- {
143
- "vehicle": {
144
- "length": 5.0,
145
- "width": 2.0,
146
- "maxPosAcc": 2.0,
147
- "maxNegAcc": 4.5,
148
- "usualPosAcc": 2.0,
149
- "usualNegAcc": 4.5,
150
- "minGap": 2.5,
151
- "maxSpeed": 16.67,
152
- "headwayTime": 1.5
153
- },
154
- "route": [
155
- "road_1_2_3",
156
- "road_1_1_2"
157
- ],
158
- "interval": 5.0,
159
- "startTime": 0,
160
- "endTime": -1
161
- },
162
- {
163
- "vehicle": {
164
- "length": 5.0,
165
- "width": 2.0,
166
- "maxPosAcc": 2.0,
167
- "maxNegAcc": 4.5,
168
- "usualPosAcc": 2.0,
169
- "usualNegAcc": 4.5,
170
- "minGap": 2.5,
171
- "maxSpeed": 16.67,
172
- "headwayTime": 1.5
173
- },
174
- "route": [
175
- "road_0_1_0",
176
- "road_1_1_3"
177
- ],
178
- "interval": 5.0,
179
- "startTime": 0,
180
- "endTime": -1
181
- },
182
- {
183
- "vehicle": {
184
- "length": 5.0,
185
- "width": 2.0,
186
- "maxPosAcc": 2.0,
187
- "maxNegAcc": 4.5,
188
- "usualPosAcc": 2.0,
189
- "usualNegAcc": 4.5,
190
- "minGap": 2.5,
191
- "maxSpeed": 16.67,
192
- "headwayTime": 1.5
193
- },
194
- "route": [
195
- "road_1_2_3",
196
- "road_1_1_0"
197
- ],
198
- "interval": 5.0,
199
- "startTime": 0,
200
- "endTime": -1
201
- },
202
- {
203
- "vehicle": {
204
- "length": 5.0,
205
- "width": 2.0,
206
- "maxPosAcc": 2.0,
207
- "maxNegAcc": 4.5,
208
- "usualPosAcc": 2.0,
209
- "usualNegAcc": 4.5,
210
- "minGap": 2.5,
211
- "maxSpeed": 16.67,
212
- "headwayTime": 1.5
213
- },
214
- "route": [
215
- "road_2_1_2",
216
- "road_1_1_1"
217
- ],
218
- "interval": 5.0,
219
- "startTime": 0,
220
- "endTime": -1
221
- },
222
- {
223
- "vehicle": {
224
- "length": 5.0,
225
- "width": 2.0,
226
- "maxPosAcc": 2.0,
227
- "maxNegAcc": 4.5,
228
- "usualPosAcc": 2.0,
229
- "usualNegAcc": 4.5,
230
- "minGap": 2.5,
231
- "maxSpeed": 16.67,
232
- "headwayTime": 1.5
233
- },
234
- "route": [
235
- "road_1_0_1",
236
- "road_1_1_2"
237
- ],
238
- "interval": 5.0,
239
- "startTime": 0,
240
- "endTime": -1
241
- }
242
  ]
 
1
+ [
2
+ {
3
+ "vehicle": {
4
+ "length": 5.0,
5
+ "width": 2.0,
6
+ "maxPosAcc": 2.0,
7
+ "maxNegAcc": 4.5,
8
+ "usualPosAcc": 2.0,
9
+ "usualNegAcc": 4.5,
10
+ "minGap": 2.5,
11
+ "maxSpeed": 16.67,
12
+ "headwayTime": 1.5
13
+ },
14
+ "route": [
15
+ "road_0_1_0",
16
+ "road_1_1_0"
17
+ ],
18
+ "interval": 5.0,
19
+ "startTime": 0,
20
+ "endTime": -1
21
+ },
22
+ {
23
+ "vehicle": {
24
+ "length": 5.0,
25
+ "width": 2.0,
26
+ "maxPosAcc": 2.0,
27
+ "maxNegAcc": 4.5,
28
+ "usualPosAcc": 2.0,
29
+ "usualNegAcc": 4.5,
30
+ "minGap": 2.5,
31
+ "maxSpeed": 16.67,
32
+ "headwayTime": 1.5
33
+ },
34
+ "route": [
35
+ "road_2_1_2",
36
+ "road_1_1_2"
37
+ ],
38
+ "interval": 5.0,
39
+ "startTime": 0,
40
+ "endTime": -1
41
+ },
42
+ {
43
+ "vehicle": {
44
+ "length": 5.0,
45
+ "width": 2.0,
46
+ "maxPosAcc": 2.0,
47
+ "maxNegAcc": 4.5,
48
+ "usualPosAcc": 2.0,
49
+ "usualNegAcc": 4.5,
50
+ "minGap": 2.5,
51
+ "maxSpeed": 16.67,
52
+ "headwayTime": 1.5
53
+ },
54
+ "route": [
55
+ "road_1_0_1",
56
+ "road_1_1_1"
57
+ ],
58
+ "interval": 5.0,
59
+ "startTime": 0,
60
+ "endTime": -1
61
+ },
62
+ {
63
+ "vehicle": {
64
+ "length": 5.0,
65
+ "width": 2.0,
66
+ "maxPosAcc": 2.0,
67
+ "maxNegAcc": 4.5,
68
+ "usualPosAcc": 2.0,
69
+ "usualNegAcc": 4.5,
70
+ "minGap": 2.5,
71
+ "maxSpeed": 16.67,
72
+ "headwayTime": 1.5
73
+ },
74
+ "route": [
75
+ "road_1_2_3",
76
+ "road_1_1_3"
77
+ ],
78
+ "interval": 5.0,
79
+ "startTime": 0,
80
+ "endTime": -1
81
+ },
82
+ {
83
+ "vehicle": {
84
+ "length": 5.0,
85
+ "width": 2.0,
86
+ "maxPosAcc": 2.0,
87
+ "maxNegAcc": 4.5,
88
+ "usualPosAcc": 2.0,
89
+ "usualNegAcc": 4.5,
90
+ "minGap": 2.5,
91
+ "maxSpeed": 16.67,
92
+ "headwayTime": 1.5
93
+ },
94
+ "route": [
95
+ "road_1_0_1",
96
+ "road_1_1_0"
97
+ ],
98
+ "interval": 5.0,
99
+ "startTime": 0,
100
+ "endTime": -1
101
+ },
102
+ {
103
+ "vehicle": {
104
+ "length": 5.0,
105
+ "width": 2.0,
106
+ "maxPosAcc": 2.0,
107
+ "maxNegAcc": 4.5,
108
+ "usualPosAcc": 2.0,
109
+ "usualNegAcc": 4.5,
110
+ "minGap": 2.5,
111
+ "maxSpeed": 16.67,
112
+ "headwayTime": 1.5
113
+ },
114
+ "route": [
115
+ "road_0_1_0",
116
+ "road_1_1_1"
117
+ ],
118
+ "interval": 5.0,
119
+ "startTime": 0,
120
+ "endTime": -1
121
+ },
122
+ {
123
+ "vehicle": {
124
+ "length": 5.0,
125
+ "width": 2.0,
126
+ "maxPosAcc": 2.0,
127
+ "maxNegAcc": 4.5,
128
+ "usualPosAcc": 2.0,
129
+ "usualNegAcc": 4.5,
130
+ "minGap": 2.5,
131
+ "maxSpeed": 16.67,
132
+ "headwayTime": 1.5
133
+ },
134
+ "route": [
135
+ "road_2_1_2",
136
+ "road_1_1_3"
137
+ ],
138
+ "interval": 5.0,
139
+ "startTime": 0,
140
+ "endTime": -1
141
+ },
142
+ {
143
+ "vehicle": {
144
+ "length": 5.0,
145
+ "width": 2.0,
146
+ "maxPosAcc": 2.0,
147
+ "maxNegAcc": 4.5,
148
+ "usualPosAcc": 2.0,
149
+ "usualNegAcc": 4.5,
150
+ "minGap": 2.5,
151
+ "maxSpeed": 16.67,
152
+ "headwayTime": 1.5
153
+ },
154
+ "route": [
155
+ "road_1_2_3",
156
+ "road_1_1_2"
157
+ ],
158
+ "interval": 5.0,
159
+ "startTime": 0,
160
+ "endTime": -1
161
+ },
162
+ {
163
+ "vehicle": {
164
+ "length": 5.0,
165
+ "width": 2.0,
166
+ "maxPosAcc": 2.0,
167
+ "maxNegAcc": 4.5,
168
+ "usualPosAcc": 2.0,
169
+ "usualNegAcc": 4.5,
170
+ "minGap": 2.5,
171
+ "maxSpeed": 16.67,
172
+ "headwayTime": 1.5
173
+ },
174
+ "route": [
175
+ "road_0_1_0",
176
+ "road_1_1_3"
177
+ ],
178
+ "interval": 5.0,
179
+ "startTime": 0,
180
+ "endTime": -1
181
+ },
182
+ {
183
+ "vehicle": {
184
+ "length": 5.0,
185
+ "width": 2.0,
186
+ "maxPosAcc": 2.0,
187
+ "maxNegAcc": 4.5,
188
+ "usualPosAcc": 2.0,
189
+ "usualNegAcc": 4.5,
190
+ "minGap": 2.5,
191
+ "maxSpeed": 16.67,
192
+ "headwayTime": 1.5
193
+ },
194
+ "route": [
195
+ "road_1_2_3",
196
+ "road_1_1_0"
197
+ ],
198
+ "interval": 5.0,
199
+ "startTime": 0,
200
+ "endTime": -1
201
+ },
202
+ {
203
+ "vehicle": {
204
+ "length": 5.0,
205
+ "width": 2.0,
206
+ "maxPosAcc": 2.0,
207
+ "maxNegAcc": 4.5,
208
+ "usualPosAcc": 2.0,
209
+ "usualNegAcc": 4.5,
210
+ "minGap": 2.5,
211
+ "maxSpeed": 16.67,
212
+ "headwayTime": 1.5
213
+ },
214
+ "route": [
215
+ "road_2_1_2",
216
+ "road_1_1_1"
217
+ ],
218
+ "interval": 5.0,
219
+ "startTime": 0,
220
+ "endTime": -1
221
+ },
222
+ {
223
+ "vehicle": {
224
+ "length": 5.0,
225
+ "width": 2.0,
226
+ "maxPosAcc": 2.0,
227
+ "maxNegAcc": 4.5,
228
+ "usualPosAcc": 2.0,
229
+ "usualNegAcc": 4.5,
230
+ "minGap": 2.5,
231
+ "maxSpeed": 16.67,
232
+ "headwayTime": 1.5
233
+ },
234
+ "route": [
235
+ "road_1_0_1",
236
+ "road_1_1_2"
237
+ ],
238
+ "interval": 5.0,
239
+ "startTime": 0,
240
+ "endTime": -1
241
+ }
242
  ]
third_party/CityFlow/frontend/Point.js CHANGED
@@ -1,57 +1,57 @@
1
- class Point {
2
- constructor(x, y) {
3
- if (arguments.length == 1) {
4
- this.x = x[0];
5
- this.y = x[1];
6
- } else if (arguments.length == 2) {
7
- this.x = x;
8
- this.y = y;
9
- }
10
- }
11
-
12
- getLength() {
13
- return Math.sqrt(this.x*this.x + this.y*this.y);
14
- }
15
-
16
- getAngle() {
17
- return this.getAngleInRadians() * 180 / Math.PI;
18
- }
19
-
20
- getAngleInRadians() {
21
- return Math.atan2(this.y, this.x);
22
- }
23
-
24
- add(point) {
25
- return new Point(this.x + point.x, this.y + point.y)
26
- }
27
-
28
- directTo(point) {
29
- let x = point.x - this.x;
30
- let y = point.y - this.y;
31
- let length = Math.sqrt(x*x + y*y);
32
- let scale = length !== 0 ? 1 / length : 0;
33
- return new Point(x * scale, y * scale);
34
- }
35
-
36
- distanceTo(point) {
37
- let x = point.x - this.x;
38
- let y = point.y - this.y;
39
- return Math.sqrt(x*x + y*y);
40
- }
41
-
42
- moveAlong(direct, length) {
43
- return new Point(this.x + direct.x * length, this.y + direct.y * length);
44
- }
45
-
46
- moveAlongDirectTo(point, length) {
47
- let direct = this.directTo(point);
48
- return new Point(this.x + direct.x * length, this.y + direct.y * length);
49
- }
50
-
51
- rotate(angle) {
52
- angle = angle * Math.PI / 180;
53
- let sin = Math.sin(angle);
54
- let cos = Math.cos(angle);
55
- return new Point(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
56
- }
57
  }
 
1
+ class Point {
2
+ constructor(x, y) {
3
+ if (arguments.length == 1) {
4
+ this.x = x[0];
5
+ this.y = x[1];
6
+ } else if (arguments.length == 2) {
7
+ this.x = x;
8
+ this.y = y;
9
+ }
10
+ }
11
+
12
+ getLength() {
13
+ return Math.sqrt(this.x*this.x + this.y*this.y);
14
+ }
15
+
16
+ getAngle() {
17
+ return this.getAngleInRadians() * 180 / Math.PI;
18
+ }
19
+
20
+ getAngleInRadians() {
21
+ return Math.atan2(this.y, this.x);
22
+ }
23
+
24
+ add(point) {
25
+ return new Point(this.x + point.x, this.y + point.y)
26
+ }
27
+
28
+ directTo(point) {
29
+ let x = point.x - this.x;
30
+ let y = point.y - this.y;
31
+ let length = Math.sqrt(x*x + y*y);
32
+ let scale = length !== 0 ? 1 / length : 0;
33
+ return new Point(x * scale, y * scale);
34
+ }
35
+
36
+ distanceTo(point) {
37
+ let x = point.x - this.x;
38
+ let y = point.y - this.y;
39
+ return Math.sqrt(x*x + y*y);
40
+ }
41
+
42
+ moveAlong(direct, length) {
43
+ return new Point(this.x + direct.x * length, this.y + direct.y * length);
44
+ }
45
+
46
+ moveAlongDirectTo(point, length) {
47
+ let direct = this.directTo(point);
48
+ return new Point(this.x + direct.x * length, this.y + direct.y * length);
49
+ }
50
+
51
+ rotate(angle) {
52
+ angle = angle * Math.PI / 180;
53
+ let sin = Math.sin(angle);
54
+ let cos = Math.cos(angle);
55
+ return new Point(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
56
+ }
57
  }
third_party/CityFlow/frontend/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # CityFlow Frontend Tool
2
-
3
- Open `index.html` to view replay.
4
-
5
- Run `python download_replay.py` to download example replay files.
6
-
7
  Checkout [Document](https://cityflow.readthedocs.io/en/latest/replay.html) for more instructions.
 
1
+ # CityFlow Frontend Tool
2
+
3
+ Open `index.html` to view replay.
4
+
5
+ Run `python download_replay.py` to download example replay files.
6
+
7
  Checkout [Document](https://cityflow.readthedocs.io/en/latest/replay.html) for more instructions.
third_party/CityFlow/frontend/download_replay.py CHANGED
@@ -1,19 +1,19 @@
1
- import os
2
- from urllib.request import urlretrieve
3
-
4
- files = ["replay.txt", "roadnet.json"]
5
- repo_url = "https://github.com/cityflow-project/data/raw/master/frontend/replay/"
6
-
7
- folder = "replay"
8
- if __name__ == '__main__':
9
- if not (os.path.exists(folder) and os.path.isdir(folder)):
10
- os.mkdir(folder)
11
- print("make %s folder, done!" % folder)
12
-
13
- for file_name in files:
14
- if os.path.exists(folder + "/" + file_name):
15
- print(file_name + " found.")
16
- else:
17
- print("retrieving %s from cityflow-project/data/frontend/replay" % file_name)
18
- urlretrieve(repo_url + file_name, folder + "/" + file_name)
19
  print("done!")
 
1
+ import os
2
+ from urllib.request import urlretrieve
3
+
4
+ files = ["replay.txt", "roadnet.json"]
5
+ repo_url = "https://github.com/cityflow-project/data/raw/master/frontend/replay/"
6
+
7
+ folder = "replay"
8
+ if __name__ == '__main__':
9
+ if not (os.path.exists(folder) and os.path.isdir(folder)):
10
+ os.mkdir(folder)
11
+ print("make %s folder, done!" % folder)
12
+
13
+ for file_name in files:
14
+ if os.path.exists(folder + "/" + file_name):
15
+ print(file_name + " found.")
16
+ else:
17
+ print("retrieving %s from cityflow-project/data/frontend/replay" % file_name)
18
+ urlretrieve(repo_url + file_name, folder + "/" + file_name)
19
  print("done!")
third_party/CityFlow/frontend/index.html CHANGED
@@ -1,136 +1,136 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Traffic Visualizer</title>
6
- <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.2/pixi.min.js"></script>
7
- <script src="https://cdn.jsdelivr.net/npm/pixi-viewport@4.2.3/dist/viewport.min.js"></script>
8
- <link rel="stylesheet" href="/static/style_multi.css?v=20260308h">
9
- <script src="/static/Point.js?v=20260308h"></script>
10
- <style>
11
- @keyframes fadeInUp {
12
- from { opacity: 0; transform: translateY(8px); }
13
- to { opacity: 1; transform: translateY(0); }
14
- }
15
- </style>
16
- </head>
17
- <body>
18
-
19
- <!-- ── Left sidebar ─────────────────────────────────────── -->
20
- <aside id="sidebar">
21
- <div>
22
- <h1>Traffic Visualizer</h1>
23
- <p class="subtitle">Multi-policy comparison dashboard</p>
24
- </div>
25
-
26
- <!-- 1. Roadnet upload -->
27
- <div class="sidebar-section">
28
- <h3>1. Upload Roadnet</h3>
29
- <div id="roadnet-drop-zone">
30
- <span class="drop-icon">&#x1F5FA;</span>
31
- <div>Drop <strong>roadnet.json</strong> here</div>
32
- <div style="font-size:11px; margin-top:4px; color:#555">or click to browse</div>
33
- <div class="drop-filename"></div>
34
- </div>
35
- <input type="file" id="roadnet-file-input" accept=".json" />
36
- </div>
37
-
38
- <!-- 2. City / Scenario -->
39
- <div class="sidebar-section">
40
- <h3>2. Select Scenario</h3>
41
- <div class="picker-row">
42
- <label for="city-select">City</label>
43
- <select id="city-select">
44
- <option value="">-- loading --</option>
45
- </select>
46
- </div>
47
- <div class="picker-row" style="margin-top:8px;">
48
- <label for="scenario-select">Scenario</label>
49
- <select id="scenario-select" disabled>
50
- <option value="">-- select city first --</option>
51
- </select>
52
- </div>
53
- </div>
54
-
55
- <!-- 3. Policies -->
56
- <div class="sidebar-section">
57
- <h3>3. Policies</h3>
58
- <div id="policy-list" class="policy-list"></div>
59
- </div>
60
-
61
- <!-- 4. View mode -->
62
- <div class="sidebar-section">
63
- <h3>4. View Mode</h3>
64
- <div class="view-mode-row">
65
- <button class="view-mode-btn active" data-panels="1">1</button>
66
- <button class="view-mode-btn" data-panels="2">2</button>
67
- <button class="view-mode-btn" data-panels="3">3</button>
68
- <button class="view-mode-btn" data-panels="6">All 6</button>
69
- </div>
70
- </div>
71
-
72
- <!-- Run -->
73
- <button id="run-btn">Run Simulations</button>
74
- <label class="force-rerun-row">
75
- <input type="checkbox" id="force-rerun-checkbox" />
76
- <span>Force rerun (ignore cache)</span>
77
- </label>
78
-
79
- <!-- Progress -->
80
- <div class="sidebar-section" id="progress-section" style="display:none;"></div>
81
-
82
- <!-- Debug log -->
83
- <div class="sidebar-section" id="debug-log-section">
84
- <h3>Debug Log</h3>
85
- <div id="debug-log" class="debug-log"></div>
86
- </div>
87
-
88
- <!-- Recent runs -->
89
- <div class="sidebar-section" id="recent-runs-section">
90
- <h3>Recent Runs</h3>
91
- <div id="recent-runs-list" class="recent-runs-list">
92
- <div class="recent-run-empty">No saved runs yet.</div>
93
- </div>
94
- </div>
95
- </aside>
96
-
97
- <div id="sidebar-resize-handle"></div>
98
-
99
- <!-- ── Main area ─────────────────────────────────────────── -->
100
- <div id="main">
101
-
102
- <!-- Playback toolbar -->
103
- <div id="playback-bar">
104
- <button id="pause-btn">Pause</button>
105
- <button id="speed-down">&#8722;</button>
106
- <input type="range" id="scrubber" min="0" max="0" value="0" />
107
- <button id="speed-up">&#43;</button>
108
- <span id="speed-label">0.5x</span>
109
- <span id="step-display">0 / 0</span>
110
- </div>
111
-
112
- <div id="summary-bar">
113
- <div class="summary-empty">Run a simulation to compare policy metrics.</div>
114
- </div>
115
-
116
- <!-- Panels container -->
117
- <div id="panels-container" class="layout-1" style="position:relative; flex:1; overflow:hidden;">
118
-
119
- <!-- Welcome state (shown until first run) -->
120
- <div id="welcome-overlay">
121
- <h2>Traffic Visualizer</h2>
122
- <p>
123
- Upload a <strong>roadnet.json</strong> and select a scenario,
124
- then click <strong>Run Simulations</strong> to compare policies
125
- side by side.
126
- </p>
127
- </div>
128
-
129
- </div>
130
-
131
-
132
- </div>
133
-
134
- <script src="/static/script_multi.js?v=20260308h"></script>
135
- </body>
136
- </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Traffic Visualizer</title>
6
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.2/pixi.min.js"></script>
7
+ <script src="https://cdn.jsdelivr.net/npm/pixi-viewport@4.2.3/dist/viewport.min.js"></script>
8
+ <link rel="stylesheet" href="/static/style_multi.css?v=20260308h">
9
+ <script src="/static/Point.js?v=20260308h"></script>
10
+ <style>
11
+ @keyframes fadeInUp {
12
+ from { opacity: 0; transform: translateY(8px); }
13
+ to { opacity: 1; transform: translateY(0); }
14
+ }
15
+ </style>
16
+ </head>
17
+ <body>
18
+
19
+ <!-- ── Left sidebar ──��───────────────────────────────────── -->
20
+ <aside id="sidebar">
21
+ <div>
22
+ <h1>Traffic Visualizer</h1>
23
+ <p class="subtitle">Multi-policy comparison dashboard</p>
24
+ </div>
25
+
26
+ <!-- 1. Roadnet upload -->
27
+ <div class="sidebar-section">
28
+ <h3>1. Upload Roadnet</h3>
29
+ <div id="roadnet-drop-zone">
30
+ <span class="drop-icon">&#x1F5FA;</span>
31
+ <div>Drop <strong>roadnet.json</strong> here</div>
32
+ <div style="font-size:11px; margin-top:4px; color:#555">or click to browse</div>
33
+ <div class="drop-filename"></div>
34
+ </div>
35
+ <input type="file" id="roadnet-file-input" accept=".json" />
36
+ </div>
37
+
38
+ <!-- 2. City / Scenario -->
39
+ <div class="sidebar-section">
40
+ <h3>2. Select Scenario</h3>
41
+ <div class="picker-row">
42
+ <label for="city-select">City</label>
43
+ <select id="city-select">
44
+ <option value="">-- loading --</option>
45
+ </select>
46
+ </div>
47
+ <div class="picker-row" style="margin-top:8px;">
48
+ <label for="scenario-select">Scenario</label>
49
+ <select id="scenario-select" disabled>
50
+ <option value="">-- select city first --</option>
51
+ </select>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- 3. Policies -->
56
+ <div class="sidebar-section">
57
+ <h3>3. Policies</h3>
58
+ <div id="policy-list" class="policy-list"></div>
59
+ </div>
60
+
61
+ <!-- 4. View mode -->
62
+ <div class="sidebar-section">
63
+ <h3>4. View Mode</h3>
64
+ <div class="view-mode-row">
65
+ <button class="view-mode-btn active" data-panels="1">1</button>
66
+ <button class="view-mode-btn" data-panels="2">2</button>
67
+ <button class="view-mode-btn" data-panels="3">3</button>
68
+ <button class="view-mode-btn" data-panels="6">All 6</button>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Run -->
73
+ <button id="run-btn">Run Simulations</button>
74
+ <label class="force-rerun-row">
75
+ <input type="checkbox" id="force-rerun-checkbox" />
76
+ <span>Force rerun (ignore cache)</span>
77
+ </label>
78
+
79
+ <!-- Progress -->
80
+ <div class="sidebar-section" id="progress-section" style="display:none;"></div>
81
+
82
+ <!-- Debug log -->
83
+ <div class="sidebar-section" id="debug-log-section">
84
+ <h3>Debug Log</h3>
85
+ <div id="debug-log" class="debug-log"></div>
86
+ </div>
87
+
88
+ <!-- Recent runs -->
89
+ <div class="sidebar-section" id="recent-runs-section">
90
+ <h3>Recent Runs</h3>
91
+ <div id="recent-runs-list" class="recent-runs-list">
92
+ <div class="recent-run-empty">No saved runs yet.</div>
93
+ </div>
94
+ </div>
95
+ </aside>
96
+
97
+ <div id="sidebar-resize-handle"></div>
98
+
99
+ <!-- ── Main area ─────────────────────────────────────────── -->
100
+ <div id="main">
101
+
102
+ <!-- Playback toolbar -->
103
+ <div id="playback-bar">
104
+ <button id="pause-btn">Pause</button>
105
+ <button id="speed-down">&#8722;</button>
106
+ <input type="range" id="scrubber" min="0" max="0" value="0" />
107
+ <button id="speed-up">&#43;</button>
108
+ <span id="speed-label">0.5x</span>
109
+ <span id="step-display">0 / 0</span>
110
+ </div>
111
+
112
+ <div id="summary-bar">
113
+ <div class="summary-empty">Run a simulation to compare policy metrics.</div>
114
+ </div>
115
+
116
+ <!-- Panels container -->
117
+ <div id="panels-container" class="layout-1" style="position:relative; flex:1; overflow:hidden;">
118
+
119
+ <!-- Welcome state (shown until first run) -->
120
+ <div id="welcome-overlay">
121
+ <h2>Traffic Visualizer</h2>
122
+ <p>
123
+ Upload a <strong>roadnet.json</strong> and select a scenario,
124
+ then click <strong>Run Simulations</strong> to compare policies
125
+ side by side.
126
+ </p>
127
+ </div>
128
+
129
+ </div>
130
+
131
+
132
+ </div>
133
+
134
+ <script src="/static/script_multi.js?v=20260308h"></script>
135
+ </body>
136
+ </html>
third_party/CityFlow/frontend/script.js CHANGED
@@ -1,1035 +1,1035 @@
1
- /**
2
- * Draw Road Network
3
- */
4
- id = Math.random().toString(36).substring(2, 15);
5
-
6
- BACKGROUND_COLOR = 0xe8ebed;
7
- LANE_COLOR = 0x586970;
8
- LANE_BORDER_WIDTH = 1;
9
- LANE_BORDER_COLOR = 0x82a8ba;
10
- LANE_INNER_COLOR = 0xbed8e8;
11
- LANE_DASH = 10;
12
- LANE_GAP = 12;
13
- TRAFFIC_LIGHT_WIDTH = 3;
14
- MAX_TRAFFIC_LIGHT_NUM = 100000;
15
- ROTATE = 90;
16
-
17
- CAR_LENGTH = 5;
18
- CAR_WIDTH = 2;
19
- CAR_COLOR = 0xe8bed4;
20
-
21
- CAR_COLORS = [0xf2bfd7, // pink
22
- 0xb7ebe4, // cyan
23
- 0xdbebb7, // blue
24
- 0xf5ddb5,
25
- 0xd4b5f5];
26
- CAR_COLORS_NUM = CAR_COLORS.length;
27
-
28
- NUM_CAR_POOL = 150000;
29
- DISTRICT_BORDER_COLOR = 0x34495e;
30
- GATEWAY_NODE_COLOR = 0xf39c12;
31
- GATEWAY_EDGE_COLOR = 0xe67e22;
32
- DISTRICT_PALETTE = [
33
- 0x1f77b4, 0xff7f0e, 0x2ca02c, 0xd62728, 0x9467bd, 0x8c564b,
34
- 0xe377c2, 0x7f7f7f, 0xbcbd22, 0x17becf, 0x393b79, 0x637939
35
- ];
36
-
37
- LIGHT_RED = 0xdb635e;
38
- LIGHT_GREEN = 0x85ee00;
39
-
40
- TURN_SIGNAL_COLOR = 0xFFFFFF;
41
- TURN_SIGNAL_WIDTH = 1;
42
- TURN_SIGNAL_LENGTH = 5;
43
-
44
- var simulation, roadnet, steps;
45
- var nodes = {};
46
- var edges = {};
47
- var logs;
48
- var gettingLog = false;
49
-
50
- let Application = PIXI.Application,
51
- Sprite = PIXI.Sprite,
52
- Graphics = PIXI.Graphics,
53
- Container = PIXI.Container,
54
- ParticleContainer = PIXI.particles.ParticleContainer,
55
- Texture = PIXI.Texture,
56
- Rectangle = PIXI.Rectangle
57
- ;
58
-
59
- var controls = new function () {
60
- this.replaySpeedMax = 1;
61
- this.replaySpeedMin = 0.01;
62
- this.replaySpeed = 0.5;
63
- this.paused = false;
64
- };
65
-
66
- var trafficLightsG = {};
67
-
68
- var app, viewport, renderer, simulatorContainer, carContainer, trafficLightContainer;
69
- var overlayContainer;
70
- var turnSignalContainer;
71
- var carPool;
72
-
73
- var cnt = 0;
74
- var frameElapsed = 0;
75
- var totalStep;
76
-
77
- var nodeCarNum = document.getElementById("car-num");
78
- var nodeProgressPercentage = document.getElementById("progress-percentage");
79
- var nodeTotalStep = document.getElementById("total-step-num");
80
- var nodeCurrentStep = document.getElementById("current-step-num");
81
- var nodeSelectedEntity = document.getElementById("selected-entity");
82
-
83
- var SPEED = 3, SCALE_SPEED = 1.01;
84
- var LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40;
85
- var MINUS = 189, EQUAL = 187, P = 80;
86
- var LEFT_BRACKET = 219, RIGHT_BRACKET = 221;
87
- var ONE = 49, TWO = 50;
88
- var SPACE = 32;
89
-
90
- var keyDown = new Set();
91
-
92
- var turnSignalTextures = [];
93
-
94
- let pauseButton = document.getElementById("pause");
95
- let nodeCanvas = document.getElementById("simulator-canvas");
96
- let replayControlDom = document.getElementById("replay-control");
97
- let replaySpeedDom = document.getElementById("replay-speed");
98
-
99
- let loading = false;
100
- let infoDOM = document.getElementById("info");
101
- let selectedDOM = document.getElementById("selected-entity");
102
-
103
- function infoAppend(msg) {
104
- infoDOM.innerText += "- " + msg + "\n";
105
- }
106
-
107
- function infoReset() {
108
- infoDOM.innerText = "";
109
- }
110
-
111
- /**
112
- * Upload files
113
- */
114
- let ready = false;
115
-
116
- let roadnetData = [];
117
- let replayData = [];
118
- let chartData = [];
119
- let districtDataRaw = [];
120
- let districtMap = null;
121
- let showDistrictOverlay = true;
122
- let showGatewayOverlay = true;
123
-
124
- function handleChooseFile(v, label_dom) {
125
- return function(evt) {
126
- let file = evt.target.files[0];
127
- label_dom.innerText = file.name;
128
- }
129
- }
130
-
131
- function uploadFile(v, file, callback) {
132
- let reader = new FileReader();
133
- reader.onloadstart = function () {
134
- infoAppend("Loading " + file.name);
135
- };
136
- reader.onerror = function() {
137
- infoAppend("Loading " + file.name + "failed");
138
- }
139
- reader.onload = function (e) {
140
- infoAppend(file.name + " loaded");
141
- v[0] = e.target.result;
142
- callback();
143
- };
144
- try {
145
- reader.readAsText(file);
146
- } catch (e) {
147
- infoAppend("Loading failed");
148
- console.error(e.message);
149
- }
150
- }
151
-
152
- let debugMode = false;
153
- let chartLog;
154
- let showChart = false;
155
- let chartConainterDOM = document.getElementById("chart-container");
156
- let showDistrictOverlayDom = document.getElementById("show-district-overlay");
157
- let showGatewayOverlayDom = document.getElementById("show-gateway-overlay");
158
-
159
- function parseDistrictMap() {
160
- if (!districtDataRaw[0]) {
161
- districtMap = null;
162
- return;
163
- }
164
- try {
165
- districtMap = JSON.parse(districtDataRaw[0]);
166
- } catch (e) {
167
- districtMap = null;
168
- infoAppend("Parsing district map file failed");
169
- }
170
- }
171
-
172
- function uploadOptionalDistrictThenChart(done) {
173
- if (DistrictFileDom.value) {
174
- uploadFile(districtDataRaw, DistrictFileDom.files[0], function() {
175
- parseDistrictMap();
176
- if (ChartFileDom.value) {
177
- showChart = true;
178
- uploadFile(chartData, ChartFileDom.files[0], done);
179
- } else {
180
- showChart = false;
181
- done();
182
- }
183
- });
184
- } else {
185
- districtMap = null;
186
- if (ChartFileDom.value) {
187
- showChart = true;
188
- uploadFile(chartData, ChartFileDom.files[0], done);
189
- } else {
190
- showChart = false;
191
- done();
192
- }
193
- }
194
- }
195
-
196
- function start() {
197
- if (loading) return;
198
- loading = true;
199
- infoReset();
200
- showDistrictOverlay = showDistrictOverlayDom.checked;
201
- showGatewayOverlay = showGatewayOverlayDom.checked;
202
- uploadFile(roadnetData, RoadnetFileDom.files[0], function(){
203
- uploadFile(replayData, ReplayFileDom.files[0], function(){
204
- let after_update = function() {
205
- infoAppend("drawing roadnet");
206
- ready = false;
207
- document.getElementById("guide").classList.add("d-none");
208
- hideCanvas();
209
- try {
210
- simulation = JSON.parse(roadnetData[0]);
211
- } catch (e) {
212
- infoAppend("Parsing roadnet file failed");
213
- loading = false;
214
- return;
215
- }
216
- try {
217
- logs = replayData[0].split('\n');
218
- logs.pop();
219
- } catch (e) {
220
- infoAppend("Reading replay file failed");
221
- loading = false;
222
- return;
223
- }
224
-
225
- totalStep = logs.length;
226
- if (showChart) {
227
- chartConainterDOM.classList.remove("d-none");
228
- let chart_lines = chartData[0].split('\n');
229
- if (chart_lines.length == 0) {
230
- infoAppend("Chart file is empty");
231
- showChart = false;
232
- }
233
- chartLog = [];
234
- for (let i = 0 ; i < totalStep ; ++i) {
235
- step_data = chart_lines[i + 1].split(/[ \t]+/);
236
- chartLog.push([]);
237
- for (let j = 0; j < step_data.length; ++j) {
238
- chartLog[i].push(parseFloat(step_data[j]));
239
- }
240
- }
241
- chart.init(chart_lines[0], chartLog[0].length, totalStep);
242
- }else {
243
- chartConainterDOM.classList.add("d-none");
244
- }
245
-
246
- controls.paused = false;
247
- cnt = 0;
248
- debugMode = document.getElementById("debug-mode").checked;
249
- setTimeout(function () {
250
- try {
251
- drawRoadnet();
252
- } catch (e) {
253
- infoAppend("Drawing roadnet failed");
254
- console.error(e.message);
255
- loading = false;
256
- return;
257
- }
258
- ready = true;
259
- loading = false;
260
- infoAppend("Start replaying");
261
- }, 200);
262
- };
263
-
264
- uploadOptionalDistrictThenChart(after_update);
265
-
266
- }); // replay callback
267
- }); // roadnet callback
268
- }
269
-
270
- let RoadnetFileDom = document.getElementById("roadnet-file");
271
- let ReplayFileDom = document.getElementById("replay-file");
272
- let ChartFileDom = document.getElementById("chart-file");
273
- let DistrictFileDom = document.getElementById("district-file");
274
-
275
- RoadnetFileDom.addEventListener("change",
276
- handleChooseFile(roadnetData, document.getElementById("roadnet-label")), false);
277
- ReplayFileDom.addEventListener("change",
278
- handleChooseFile(replayData, document.getElementById("replay-label")), false);
279
- ChartFileDom.addEventListener("change",
280
- handleChooseFile(chartData, document.getElementById("chart-label")), false);
281
- DistrictFileDom.addEventListener("change",
282
- handleChooseFile(districtDataRaw, document.getElementById("district-label")), false);
283
-
284
- showDistrictOverlayDom.addEventListener("change", function(evt) {
285
- showDistrictOverlay = evt.target.checked;
286
- if (ready) drawRoadnet();
287
- });
288
- showGatewayOverlayDom.addEventListener("change", function(evt) {
289
- showGatewayOverlay = evt.target.checked;
290
- if (ready) drawRoadnet();
291
- });
292
-
293
- document.getElementById("start-btn").addEventListener("click", start);
294
-
295
- document.getElementById("slow-btn").addEventListener("click", function() {
296
- updateReplaySpeed(controls.replaySpeed - 0.1);
297
- })
298
-
299
- document.getElementById("fast-btn").addEventListener("click", function() {
300
- updateReplaySpeed(controls.replaySpeed + 0.1);
301
- })
302
-
303
- function updateReplaySpeed(speed){
304
- speed = Math.min(speed, 1);
305
- speed = Math.max(speed, 0);
306
- controls.replaySpeed = speed;
307
- replayControlDom.value = speed * 100;
308
- replaySpeedDom.innerHTML = speed.toFixed(2);
309
- }
310
-
311
- updateReplaySpeed(0.5);
312
-
313
- replayControlDom.addEventListener('change', function(e){
314
- updateReplaySpeed(replayControlDom.value / 100);
315
- });
316
-
317
- document.addEventListener('keydown', function(e) {
318
- if (e.keyCode == P) {
319
- controls.paused = !controls.paused;
320
- } else if (e.keyCode == ONE) {
321
- updateReplaySpeed(Math.max(controls.replaySpeed / 1.5, controls.replaySpeedMin));
322
- } else if (e.keyCode == TWO ) {
323
- updateReplaySpeed(Math.min(controls.replaySpeed * 1.5, controls.replaySpeedMax));
324
- } else if (e.keyCode == LEFT_BRACKET) {
325
- cnt = (cnt - 1) % totalStep;
326
- cnt = (cnt + totalStep) % totalStep;
327
- drawStep(cnt);
328
- } else if (e.keyCode == RIGHT_BRACKET) {
329
- cnt = (cnt + 1) % totalStep;
330
- drawStep(cnt);
331
- } else {
332
- keyDown.add(e.keyCode)
333
- }
334
- });
335
-
336
- document.addEventListener('keyup', (e) => keyDown.delete(e.keyCode));
337
-
338
- nodeCanvas.addEventListener('dblclick', function(e){
339
- controls.paused = !controls.paused;
340
- });
341
-
342
- pauseButton.addEventListener('click', function(e){
343
- controls.paused = !controls.paused;
344
- });
345
-
346
- function initCanvas() {
347
- app = new Application({
348
- width: nodeCanvas.offsetWidth,
349
- height: nodeCanvas.offsetHeight,
350
- transparent: false,
351
- backgroundColor: BACKGROUND_COLOR
352
- });
353
-
354
- nodeCanvas.appendChild(app.view);
355
- app.view.classList.add("d-none");
356
-
357
- renderer = app.renderer;
358
- renderer.interactive = true;
359
- renderer.autoResize = true;
360
-
361
- renderer.resize(nodeCanvas.offsetWidth, nodeCanvas.offsetHeight);
362
- app.ticker.add(run);
363
- }
364
-
365
- function showCanvas() {
366
- document.getElementById("spinner").classList.add("d-none");
367
- app.view.classList.remove("d-none");
368
- }
369
-
370
- function hideCanvas() {
371
- document.getElementById("spinner").classList.remove("d-none");
372
- app.view.classList.add("d-none");
373
- }
374
-
375
- function drawRoadnet() {
376
- if (simulatorContainer) {
377
- simulatorContainer.destroy(true);
378
- }
379
- app.stage.removeChildren();
380
- viewport = new Viewport.Viewport({
381
- screenWidth: window.innerWidth,
382
- screenHeight: window.innerHeight,
383
- interaction: app.renderer.plugins.interaction
384
- });
385
- viewport
386
- .drag()
387
- .pinch()
388
- .wheel()
389
- .decelerate();
390
- app.stage.addChild(viewport);
391
- simulatorContainer = new Container();
392
- viewport.addChild(simulatorContainer);
393
-
394
- roadnet = simulation.static;
395
- nodes = [];
396
- edges = [];
397
- trafficLightsG = {};
398
-
399
- for (let i = 0, len = roadnet.nodes.length;i < len;++i) {
400
- node = roadnet.nodes[i];
401
- node.point = new Point(transCoord(node.point));
402
- nodes[node.id] = node;
403
- }
404
-
405
- for (let i = 0, len = roadnet.edges.length;i < len;++i) {
406
- edge = roadnet.edges[i];
407
- edge.from = nodes[edge.from];
408
- edge.to = nodes[edge.to];
409
- for (let j = 0, len = edge.points.length;j < len;++j) {
410
- edge.points[j] = new Point(transCoord(edge.points[j]));
411
- }
412
- edges[edge.id] = edge;
413
- }
414
-
415
- /**
416
- * Draw Map
417
- */
418
- trafficLightContainer = new ParticleContainer(MAX_TRAFFIC_LIGHT_NUM, {tint: true});
419
- let mapContainer, mapGraphics;
420
- if (debugMode) {
421
- mapContainer = new Container();
422
- simulatorContainer.addChild(mapContainer);
423
- }else {
424
- mapGraphics = new Graphics();
425
- simulatorContainer.addChild(mapGraphics);
426
- }
427
-
428
- for (nodeId in nodes) {
429
- if (!nodes[nodeId].virtual) {
430
- let nodeGraphics;
431
- if (debugMode) {
432
- nodeGraphics = new Graphics();
433
- mapContainer.addChild(nodeGraphics);
434
- } else {
435
- nodeGraphics = mapGraphics;
436
- }
437
- drawNode(nodes[nodeId], nodeGraphics);
438
- }
439
- }
440
- for (edgeId in edges) {
441
- let edgeGraphics;
442
- if (debugMode) {
443
- edgeGraphics = new Graphics();
444
- mapContainer.addChild(edgeGraphics);
445
- } else {
446
- edgeGraphics = mapGraphics;
447
- }
448
- drawEdge(edges[edgeId], edgeGraphics);
449
- }
450
- drawOverlayLayers();
451
- let bounds = simulatorContainer.getBounds();
452
- simulatorContainer.pivot.set(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
453
- simulatorContainer.position.set(renderer.width / 2, renderer.height / 2);
454
- simulatorContainer.addChild(trafficLightContainer);
455
-
456
- /**
457
- * Settings for Cars
458
- */
459
- TURN_SIGNAL_LENGTH = CAR_LENGTH;
460
- TURN_SIGNAL_WIDTH = CAR_WIDTH / 2;
461
-
462
- var carG = new Graphics();
463
- carG.lineStyle(0);
464
- carG.beginFill(0xFFFFFF, 0.8);
465
- carG.drawRect(0, 0, CAR_LENGTH, CAR_WIDTH);
466
-
467
- let carTexture = renderer.generateTexture(carG);
468
-
469
- let signalG = new Graphics();
470
- signalG.beginFill(TURN_SIGNAL_COLOR, 0.7).drawRect(0,0, TURN_SIGNAL_LENGTH, TURN_SIGNAL_WIDTH)
471
- .drawRect(0, 3 * CAR_WIDTH - TURN_SIGNAL_WIDTH, TURN_SIGNAL_LENGTH, TURN_SIGNAL_WIDTH).endFill();
472
- let turnSignalTexture = renderer.generateTexture(signalG);
473
-
474
- let signalLeft = new Texture(turnSignalTexture, new Rectangle(0, 0, TURN_SIGNAL_LENGTH, CAR_WIDTH));
475
- let signalStraight = new Texture(turnSignalTexture, new Rectangle(0, CAR_WIDTH, TURN_SIGNAL_LENGTH, CAR_WIDTH));
476
- let signalRight = new Texture(turnSignalTexture, new Rectangle(0, CAR_WIDTH * 2, TURN_SIGNAL_LENGTH, CAR_WIDTH));
477
- turnSignalTextures = [signalLeft, signalStraight, signalRight];
478
-
479
-
480
- carPool = [];
481
- if (debugMode)
482
- carContainer = new Container();
483
- else
484
- carContainer = new ParticleContainer(NUM_CAR_POOL, {rotation: true, tint: true});
485
-
486
-
487
- turnSignalContainer = new ParticleContainer(NUM_CAR_POOL, {rotation: true, tint: true});
488
- simulatorContainer.addChild(carContainer);
489
- simulatorContainer.addChild(turnSignalContainer);
490
- for (let i = 0, len = NUM_CAR_POOL;i < len;++i) {
491
- //var car = Sprite.fromImage("images/car.png")
492
- let car = new Sprite(carTexture);
493
- let signal = new Sprite(turnSignalTextures[1]);
494
- car.anchor.set(1, 0.5);
495
-
496
- if (debugMode) {
497
- car.interactive = true;
498
- car.on('mouseover', function () {
499
- selectedDOM.innerText = car.name;
500
- car.alpha = 0.8;
501
- });
502
- car.on('mouseout', function () {
503
- // selectedDOM.innerText = "";
504
- car.alpha = 1;
505
- });
506
- }
507
- signal.anchor.set(1, 0.5);
508
- carPool.push([car, signal]);
509
- }
510
- showCanvas();
511
-
512
- return true;
513
- }
514
-
515
- function appendText(id, text) {
516
- let p = document.createElement("span");
517
- p.innerText = text;
518
- document.getElementById("info").appendChild(p);
519
- document.getElementById("info").appendChild(document.createElement("br"));
520
- }
521
-
522
- var statsFile = "";
523
- var withRange = false;
524
- var nodeStats, nodeRange;
525
-
526
- initCanvas();
527
-
528
-
529
- function transCoord(point) {
530
- return [point[0], -point[1]];
531
- }
532
-
533
- function getDistrictColor(districtId) {
534
- return DISTRICT_PALETTE[stringHash(districtId) % DISTRICT_PALETTE.length];
535
- }
536
-
537
- function cross2D(o, a, b) {
538
- return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
539
- }
540
-
541
- function convexHull(points) {
542
- if (!points || points.length <= 2) return points ? points.slice() : [];
543
- let sorted = points
544
- .slice()
545
- .sort((p, q) => (p.x === q.x ? p.y - q.y : p.x - q.x));
546
- let lower = [];
547
- for (let i = 0; i < sorted.length; ++i) {
548
- while (lower.length >= 2 && cross2D(lower[lower.length - 2], lower[lower.length - 1], sorted[i]) <= 0) {
549
- lower.pop();
550
- }
551
- lower.push(sorted[i]);
552
- }
553
- let upper = [];
554
- for (let i = sorted.length - 1; i >= 0; --i) {
555
- while (upper.length >= 2 && cross2D(upper[upper.length - 2], upper[upper.length - 1], sorted[i]) <= 0) {
556
- upper.pop();
557
- }
558
- upper.push(sorted[i]);
559
- }
560
- lower.pop();
561
- upper.pop();
562
- return lower.concat(upper);
563
- }
564
-
565
- function drawDistrictRegionFills(graphics, districtToPoints) {
566
- for (let districtId in districtToPoints) {
567
- let pts = districtToPoints[districtId];
568
- if (!pts || pts.length === 0) continue;
569
- let color = getDistrictColor(districtId);
570
- if (pts.length >= 3) {
571
- let hull = convexHull(pts);
572
- if (hull.length >= 3) {
573
- graphics.lineStyle(1.2, color, 0.35);
574
- graphics.beginFill(color, 0.11);
575
- graphics.moveTo(hull[0].x, hull[0].y);
576
- for (let i = 1; i < hull.length; ++i) {
577
- graphics.lineTo(hull[i].x, hull[i].y);
578
- }
579
- graphics.lineTo(hull[0].x, hull[0].y);
580
- graphics.endFill();
581
- continue;
582
- }
583
- }
584
- if (pts.length === 2) {
585
- graphics.lineStyle(18, color, 0.10);
586
- graphics.drawLine(pts[0], pts[1]);
587
- continue;
588
- }
589
- graphics.lineStyle(0);
590
- graphics.beginFill(color, 0.12);
591
- graphics.drawCircle(pts[0].x, pts[0].y, 18);
592
- graphics.endFill();
593
- }
594
- }
595
-
596
- function drawEdgePolyline(graphics, edge, width, color, alpha) {
597
- graphics.lineStyle(width, color, alpha);
598
- for (let i = 1; i < edge.points.length; ++i) {
599
- graphics.drawLine(edge.points[i - 1], edge.points[i]);
600
- }
601
- }
602
-
603
- function drawOverlayLayers() {
604
- if (overlayContainer) {
605
- overlayContainer.destroy(true);
606
- overlayContainer = null;
607
- }
608
- if (!showDistrictOverlay && !showGatewayOverlay) {
609
- return;
610
- }
611
- overlayContainer = new Container();
612
- simulatorContainer.addChild(overlayContainer);
613
-
614
- let overlayGraphics = new Graphics();
615
- overlayContainer.addChild(overlayGraphics);
616
-
617
- let intersectionToDistrict = {};
618
- if (districtMap && districtMap.intersection_to_district) {
619
- intersectionToDistrict = districtMap.intersection_to_district;
620
- }
621
- let gatewayNodeIds = new Set(
622
- districtMap && districtMap.gateway_intersections
623
- ? districtMap.gateway_intersections
624
- : []
625
- );
626
- let gatewayEdgeIds = new Set(
627
- districtMap && districtMap.gateway_roads
628
- ? districtMap.gateway_roads
629
- : []
630
- );
631
- let nodeDegree = {};
632
- for (let edgeId in edges) {
633
- let edge = edges[edgeId];
634
- nodeDegree[edge.from.id] = (nodeDegree[edge.from.id] || 0) + 1;
635
- nodeDegree[edge.to.id] = (nodeDegree[edge.to.id] || 0) + 1;
636
- }
637
- for (let nodeId in nodes) {
638
- if ((nodeDegree[nodeId] || 0) <= 1) {
639
- gatewayNodeIds.add(nodeId);
640
- }
641
- }
642
-
643
- if (showDistrictOverlay && districtMap && districtMap.intersection_to_district) {
644
- let districtToPoints = {};
645
- for (let nodeId in nodes) {
646
- let districtId = intersectionToDistrict[nodeId];
647
- if (!districtId) continue;
648
- if (!districtToPoints[districtId]) districtToPoints[districtId] = [];
649
- districtToPoints[districtId].push(nodes[nodeId].point);
650
- }
651
- drawDistrictRegionFills(overlayGraphics, districtToPoints);
652
-
653
- for (let edgeId in edges) {
654
- let edge = edges[edgeId];
655
- let fromDistrict = intersectionToDistrict[edge.from.id];
656
- let toDistrict = intersectionToDistrict[edge.to.id];
657
- if (!fromDistrict || !toDistrict) continue;
658
- if (fromDistrict === toDistrict) {
659
- drawEdgePolyline(
660
- overlayGraphics,
661
- edge,
662
- 1.2,
663
- getDistrictColor(fromDistrict),
664
- 0.45
665
- );
666
- } else {
667
- drawEdgePolyline(
668
- overlayGraphics,
669
- edge,
670
- 1.8,
671
- DISTRICT_BORDER_COLOR,
672
- 0.8
673
- );
674
- }
675
- }
676
- for (let nodeId in nodes) {
677
- let districtId = intersectionToDistrict[nodeId];
678
- if (!districtId) continue;
679
- let point = nodes[nodeId].point;
680
- overlayGraphics.beginFill(getDistrictColor(districtId), 0.55);
681
- overlayGraphics.drawCircle(point.x, point.y, 2.0);
682
- overlayGraphics.endFill();
683
- }
684
- }
685
-
686
- if (showGatewayOverlay) {
687
- for (let edgeId in edges) {
688
- let edge = edges[edgeId];
689
- let fromGateway = gatewayNodeIds.has(edge.from.id) || String(edge.from.id).indexOf("g_") === 0;
690
- let toGateway = gatewayNodeIds.has(edge.to.id) || String(edge.to.id).indexOf("g_") === 0;
691
- let edgeLooksGateway = edgeId.indexOf("_g_") >= 0 || edgeId.indexOf("r_g_") === 0;
692
- if (
693
- gatewayEdgeIds.has(edgeId)
694
- || fromGateway
695
- || toGateway
696
- || edgeLooksGateway
697
- ) {
698
- drawEdgePolyline(
699
- overlayGraphics,
700
- edge,
701
- 5.6,
702
- GATEWAY_EDGE_COLOR,
703
- 0.82
704
- );
705
- }
706
- }
707
- for (let nodeId in nodes) {
708
- if (gatewayNodeIds.has(nodeId) || String(nodeId).indexOf("g_") === 0) {
709
- let point = nodes[nodeId].point;
710
- overlayGraphics.lineStyle(0);
711
- overlayGraphics.beginFill(GATEWAY_NODE_COLOR, 0.28);
712
- overlayGraphics.drawCircle(point.x, point.y, 34.0);
713
- overlayGraphics.endFill();
714
- overlayGraphics.lineStyle(2.2, 0x1f2d3d, 0.98);
715
- overlayGraphics.beginFill(GATEWAY_NODE_COLOR, 0.96);
716
- overlayGraphics.drawCircle(point.x, point.y, 13.0);
717
- overlayGraphics.endFill();
718
- overlayGraphics.lineStyle(0);
719
- overlayGraphics.beginFill(0xffffff, 0.92);
720
- overlayGraphics.drawCircle(point.x, point.y, 5.2);
721
- overlayGraphics.endFill();
722
- }
723
- }
724
- }
725
- }
726
-
727
- PIXI.Graphics.prototype.drawLine = function(pointA, pointB) {
728
- this.moveTo(pointA.x, pointA.y);
729
- this.lineTo(pointB.x, pointB.y);
730
- }
731
-
732
- PIXI.Graphics.prototype.drawDashLine = function(pointA, pointB, dash = 16, gap = 8) {
733
- let direct = pointA.directTo(pointB);
734
- let distance = pointA.distanceTo(pointB);
735
-
736
- let currentPoint = pointA;
737
- let currentDistance = 0;
738
- let length;
739
- let finish = false;
740
- while (true) {
741
- this.moveTo(currentPoint.x, currentPoint.y);
742
- if (currentDistance + dash >= distance) {
743
- length = distance - currentDistance;
744
- finish = true;
745
- } else {
746
- length = dash
747
- }
748
- currentPoint = currentPoint.moveAlong(direct, length);
749
- this.lineTo(currentPoint.x, currentPoint.y);
750
- if (finish) break;
751
- currentDistance += length;
752
-
753
- if (currentDistance + gap >= distance) {
754
- break;
755
- } else {
756
- currentPoint = currentPoint.moveAlong(direct, gap);
757
- currentDistance += gap;
758
- }
759
- }
760
- };
761
-
762
- function drawNode(node, graphics) {
763
- graphics.beginFill(LANE_COLOR);
764
- let outline = node.outline;
765
- for (let i = 0 ; i < outline.length ; i+=2) {
766
- outline[i+1] = -outline[i+1];
767
- if (i == 0)
768
- graphics.moveTo(outline[i], outline[i+1]);
769
- else
770
- graphics.lineTo(outline[i], outline[i+1]);
771
- }
772
- graphics.endFill();
773
-
774
- if (debugMode) {
775
- graphics.hitArea = new PIXI.Polygon(outline);
776
- graphics.interactive = true;
777
- graphics.on("mouseover", function () {
778
- selectedDOM.innerText = node.id;
779
- graphics.alpha = 0.5;
780
- });
781
- graphics.on("mouseout", function () {
782
- graphics.alpha = 1;
783
- });
784
- }
785
-
786
- }
787
-
788
- function drawEdge(edge, graphics) {
789
- let from = edge.from;
790
- let to = edge.to;
791
- let points = edge.points;
792
-
793
- let pointA, pointAOffset, pointB, pointBOffset;
794
- let prevPointBOffset = null;
795
-
796
- let roadWidth = 0;
797
- edge.laneWidths.forEach(function(l){
798
- roadWidth += l;
799
- }, 0);
800
-
801
- let coords = [], coords1 = [];
802
-
803
- for (let i = 1;i < points.length;++i) {
804
- if (i == 1){
805
- pointA = points[0].moveAlongDirectTo(points[1], from.virtual ? 0 : from.width);
806
- pointAOffset = points[0].directTo(points[1]).rotate(ROTATE);
807
- } else {
808
- pointA = points[i-1];
809
- pointAOffset = prevPointBOffset;
810
- }
811
- if (i == points.length - 1) {
812
- pointB = points[i].moveAlongDirectTo(points[i-1], to.virtual ? 0 : to.width);
813
- pointBOffset = points[i-1].directTo(points[i]).rotate(ROTATE);
814
- } else {
815
- pointB = points[i];
816
- pointBOffset = points[i-1].directTo(points[i+1]).rotate(ROTATE);
817
- }
818
- prevPointBOffset = pointBOffset;
819
-
820
- lightG = new Graphics();
821
- lightG.lineStyle(TRAFFIC_LIGHT_WIDTH, 0xFFFFFF);
822
- lightG.drawLine(new Point(0, 0), new Point(1, 0));
823
- lightTexture = renderer.generateTexture(lightG);
824
-
825
- // Draw Traffic Lights
826
- if (i == points.length-1 && !to.virtual) {
827
- edgeTrafficLights = [];
828
- prevOffset = offset = 0;
829
- for (lane = 0;lane < edge.nLane;++lane) {
830
- offset += edge.laneWidths[lane];
831
- var light = new Sprite(lightTexture);
832
- light.anchor.set(0, 0.5);
833
- light.scale.set(offset - prevOffset, 1);
834
- point_ = pointB.moveAlong(pointBOffset, prevOffset);
835
- light.position.set(point_.x, point_.y);
836
- light.rotation = pointBOffset.getAngleInRadians();
837
- edgeTrafficLights.push(light);
838
- prevOffset = offset;
839
- trafficLightContainer.addChild(light);
840
- }
841
- trafficLightsG[edge.id] = edgeTrafficLights;
842
- }
843
-
844
- // Draw Roads
845
- graphics.lineStyle(LANE_BORDER_WIDTH, LANE_BORDER_COLOR, 1);
846
- graphics.drawLine(pointA, pointB);
847
-
848
- pointA1 = pointA.moveAlong(pointAOffset, roadWidth);
849
- pointB1 = pointB.moveAlong(pointBOffset, roadWidth);
850
-
851
- graphics.lineStyle(0);
852
- graphics.beginFill(LANE_COLOR);
853
-
854
- coords = coords.concat([pointA.x, pointA.y, pointB.x, pointB.y]);
855
- coords1 = coords1.concat([pointA1.y, pointA1.x, pointB1.y, pointB1.x]);
856
-
857
- graphics.drawPolygon([pointA.x, pointA.y, pointB.x, pointB.y, pointB1.x, pointB1.y, pointA1.x, pointA1.y]);
858
- graphics.endFill();
859
-
860
- offset = 0;
861
- for (let lane = 0, len = edge.nLane-1;lane < len;++lane) {
862
- offset += edge.laneWidths[lane];
863
- graphics.lineStyle(LANE_BORDER_WIDTH, LANE_INNER_COLOR);
864
- graphics.drawDashLine(pointA.moveAlong(pointAOffset, offset), pointB.moveAlong(pointBOffset, offset), LANE_DASH, LANE_GAP);
865
- }
866
-
867
- offset += edge.laneWidths[edge.nLane-1];
868
-
869
- // graphics.lineStyle(LANE_BORDER_WIDTH, LANE_BORDER_COLOR);
870
- // graphics.drawLine(pointA.moveAlong(pointAOffset, offset), pointB.moveAlong(pointBOffset, offset));
871
- }
872
-
873
- if (debugMode) {
874
- coords = coords.concat(coords1.reverse());
875
- graphics.interactive = true;
876
- graphics.hitArea = new PIXI.Polygon(coords);
877
- graphics.on("mouseover", function () {
878
- graphics.alpha = 0.5;
879
- selectedDOM.innerText = edge.id;
880
- });
881
-
882
- graphics.on("mouseout", function () {
883
- graphics.alpha = 1;
884
- });
885
- }
886
- }
887
-
888
- function run(delta) {
889
- let redraw = false;
890
-
891
- if (ready && (!controls.paused || redraw)) {
892
- try {
893
- drawStep(cnt);
894
- }catch (e) {
895
- infoAppend("Error occurred when drawing");
896
- ready = false;
897
- }
898
- if (!controls.paused) {
899
- frameElapsed += 1;
900
- if (frameElapsed >= 1 / controls.replaySpeed ** 2) {
901
- cnt += 1;
902
- frameElapsed = 0;
903
- if (cnt == totalStep) cnt = 0;
904
- }
905
- }
906
- }
907
- }
908
-
909
- function _statusToColor(status) {
910
- switch (status) {
911
- case 'r':
912
- return LIGHT_RED;
913
- case 'g':
914
- return LIGHT_GREEN;
915
- default:
916
- return 0x808080;
917
- }
918
- }
919
-
920
- function stringHash(str) {
921
- let hash = 0;
922
- let p = 127, p_pow = 1;
923
- let m = 1e9 + 9;
924
- for (let i = 0; i < str.length; i++) {
925
- hash = (hash + str.charCodeAt(i) * p_pow) % m;
926
- p_pow = (p_pow * p) % m;
927
- }
928
- return hash;
929
- }
930
-
931
- function drawStep(step) {
932
- if (showChart && (step > chart.ptr || step == 0)) {
933
- if (step == 0) {
934
- chart.clear();
935
- }
936
- chart.ptr = step;
937
- chart.addData(chartLog[step]);
938
- }
939
-
940
- let [carLogs, tlLogs] = logs[step].split(';');
941
-
942
- tlLogs = tlLogs.split(',');
943
- carLogs = carLogs.split(',');
944
-
945
- let tlLog, tlEdge, tlStatus;
946
- for (let i = 0, len = tlLogs.length;i < len;++i) {
947
- tlLog = tlLogs[i].split(' ');
948
- tlEdge = tlLog[0];
949
- tlStatus = tlLog.slice(1);
950
- for (let j = 0, len = tlStatus.length;j < len;++j) {
951
- trafficLightsG[tlEdge][j].tint = _statusToColor(tlStatus[j]);
952
- if (tlStatus[j] == 'i' ) {
953
- trafficLightsG[tlEdge][j].alpha = 0;
954
- }else{
955
- trafficLightsG[tlEdge][j].alpha = 1;
956
- }
957
- }
958
- }
959
-
960
- carContainer.removeChildren();
961
- turnSignalContainer.removeChildren();
962
- let carLog, position, length, width;
963
- for (let i = 0, len = carLogs.length - 1;i < len;++i) {
964
- carLog = carLogs[i].split(' ');
965
- position = transCoord([parseFloat(carLog[0]), parseFloat(carLog[1])]);
966
- length = parseFloat(carLog[5]);
967
- width = parseFloat(carLog[6]);
968
- carPool[i][0].position.set(position[0], position[1]);
969
- carPool[i][0].rotation = 2*Math.PI - parseFloat(carLog[2]);
970
- carPool[i][0].name = carLog[3];
971
- let carColorId = stringHash(carLog[3]) % CAR_COLORS_NUM;
972
- carPool[i][0].tint = CAR_COLORS[carColorId];
973
- carPool[i][0].width = length;
974
- carPool[i][0].height = width;
975
- carContainer.addChild(carPool[i][0]);
976
-
977
- let laneChange = parseInt(carLog[4]) + 1;
978
- carPool[i][1].position.set(position[0], position[1]);
979
- carPool[i][1].rotation = carPool[i][0].rotation;
980
- carPool[i][1].texture = turnSignalTextures[laneChange];
981
- carPool[i][1].width = length;
982
- carPool[i][1].height = width;
983
- turnSignalContainer.addChild(carPool[i][1]);
984
- }
985
- nodeCarNum.innerText = carLogs.length-1;
986
- nodeTotalStep.innerText = totalStep;
987
- nodeCurrentStep.innerText = cnt+1;
988
- nodeProgressPercentage.innerText = (cnt / totalStep * 100).toFixed(2) + "%";
989
- if (statsFile != "") {
990
- if (withRange) nodeRange.value = stats[step][1];
991
- nodeStats.innerText = stats[step][0].toFixed(2);
992
- }
993
- }
994
-
995
- /*
996
- Chart
997
- */
998
- let chart = {
999
- max_steps: 3600,
1000
- data: {
1001
- labels: [],
1002
- series: [[]]
1003
- },
1004
- options: {
1005
- showPoint: false,
1006
- lineSmooth: false,
1007
- axisX: {
1008
- showGrid: false,
1009
- showLabel: false
1010
- }
1011
- },
1012
- init : function(title, series_cnt, max_step){
1013
- document.getElementById("chart-title").innerText = title;
1014
- this.max_steps = max_step;
1015
- this.data.labels = new Array(this.max_steps);
1016
- this.data.series = [];
1017
- for (let i = 0 ; i < series_cnt ; ++i)
1018
- this.data.series.push([]);
1019
- this.chart = new Chartist.Line('#chart', this.data, this.options);
1020
- },
1021
- addData: function (value) {
1022
- for (let i = 0 ; i < value.length; ++i) {
1023
- this.data.series[i].push(value[i]);
1024
- if (this.data.series[i].length > this.max_steps) {
1025
- this.data.series[i].shift();
1026
- }
1027
- }
1028
- this.chart.update();
1029
- },
1030
- clear: function() {
1031
- for (let i = 0 ; i < this.data.series.length ; ++i)
1032
- this.data.series[i] = [];
1033
- },
1034
- ptr: 0
1035
- };
 
1
+ /**
2
+ * Draw Road Network
3
+ */
4
+ id = Math.random().toString(36).substring(2, 15);
5
+
6
+ BACKGROUND_COLOR = 0xe8ebed;
7
+ LANE_COLOR = 0x586970;
8
+ LANE_BORDER_WIDTH = 1;
9
+ LANE_BORDER_COLOR = 0x82a8ba;
10
+ LANE_INNER_COLOR = 0xbed8e8;
11
+ LANE_DASH = 10;
12
+ LANE_GAP = 12;
13
+ TRAFFIC_LIGHT_WIDTH = 3;
14
+ MAX_TRAFFIC_LIGHT_NUM = 100000;
15
+ ROTATE = 90;
16
+
17
+ CAR_LENGTH = 5;
18
+ CAR_WIDTH = 2;
19
+ CAR_COLOR = 0xe8bed4;
20
+
21
+ CAR_COLORS = [0xf2bfd7, // pink
22
+ 0xb7ebe4, // cyan
23
+ 0xdbebb7, // blue
24
+ 0xf5ddb5,
25
+ 0xd4b5f5];
26
+ CAR_COLORS_NUM = CAR_COLORS.length;
27
+
28
+ NUM_CAR_POOL = 150000;
29
+ DISTRICT_BORDER_COLOR = 0x34495e;
30
+ GATEWAY_NODE_COLOR = 0xf39c12;
31
+ GATEWAY_EDGE_COLOR = 0xe67e22;
32
+ DISTRICT_PALETTE = [
33
+ 0x1f77b4, 0xff7f0e, 0x2ca02c, 0xd62728, 0x9467bd, 0x8c564b,
34
+ 0xe377c2, 0x7f7f7f, 0xbcbd22, 0x17becf, 0x393b79, 0x637939
35
+ ];
36
+
37
+ LIGHT_RED = 0xdb635e;
38
+ LIGHT_GREEN = 0x85ee00;
39
+
40
+ TURN_SIGNAL_COLOR = 0xFFFFFF;
41
+ TURN_SIGNAL_WIDTH = 1;
42
+ TURN_SIGNAL_LENGTH = 5;
43
+
44
+ var simulation, roadnet, steps;
45
+ var nodes = {};
46
+ var edges = {};
47
+ var logs;
48
+ var gettingLog = false;
49
+
50
+ let Application = PIXI.Application,
51
+ Sprite = PIXI.Sprite,
52
+ Graphics = PIXI.Graphics,
53
+ Container = PIXI.Container,
54
+ ParticleContainer = PIXI.particles.ParticleContainer,
55
+ Texture = PIXI.Texture,
56
+ Rectangle = PIXI.Rectangle
57
+ ;
58
+
59
+ var controls = new function () {
60
+ this.replaySpeedMax = 1;
61
+ this.replaySpeedMin = 0.01;
62
+ this.replaySpeed = 0.5;
63
+ this.paused = false;
64
+ };
65
+
66
+ var trafficLightsG = {};
67
+
68
+ var app, viewport, renderer, simulatorContainer, carContainer, trafficLightContainer;
69
+ var overlayContainer;
70
+ var turnSignalContainer;
71
+ var carPool;
72
+
73
+ var cnt = 0;
74
+ var frameElapsed = 0;
75
+ var totalStep;
76
+
77
+ var nodeCarNum = document.getElementById("car-num");
78
+ var nodeProgressPercentage = document.getElementById("progress-percentage");
79
+ var nodeTotalStep = document.getElementById("total-step-num");
80
+ var nodeCurrentStep = document.getElementById("current-step-num");
81
+ var nodeSelectedEntity = document.getElementById("selected-entity");
82
+
83
+ var SPEED = 3, SCALE_SPEED = 1.01;
84
+ var LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40;
85
+ var MINUS = 189, EQUAL = 187, P = 80;
86
+ var LEFT_BRACKET = 219, RIGHT_BRACKET = 221;
87
+ var ONE = 49, TWO = 50;
88
+ var SPACE = 32;
89
+
90
+ var keyDown = new Set();
91
+
92
+ var turnSignalTextures = [];
93
+
94
+ let pauseButton = document.getElementById("pause");
95
+ let nodeCanvas = document.getElementById("simulator-canvas");
96
+ let replayControlDom = document.getElementById("replay-control");
97
+ let replaySpeedDom = document.getElementById("replay-speed");
98
+
99
+ let loading = false;
100
+ let infoDOM = document.getElementById("info");
101
+ let selectedDOM = document.getElementById("selected-entity");
102
+
103
+ function infoAppend(msg) {
104
+ infoDOM.innerText += "- " + msg + "\n";
105
+ }
106
+
107
+ function infoReset() {
108
+ infoDOM.innerText = "";
109
+ }
110
+
111
+ /**
112
+ * Upload files
113
+ */
114
+ let ready = false;
115
+
116
+ let roadnetData = [];
117
+ let replayData = [];
118
+ let chartData = [];
119
+ let districtDataRaw = [];
120
+ let districtMap = null;
121
+ let showDistrictOverlay = true;
122
+ let showGatewayOverlay = true;
123
+
124
+ function handleChooseFile(v, label_dom) {
125
+ return function(evt) {
126
+ let file = evt.target.files[0];
127
+ label_dom.innerText = file.name;
128
+ }
129
+ }
130
+
131
+ function uploadFile(v, file, callback) {
132
+ let reader = new FileReader();
133
+ reader.onloadstart = function () {
134
+ infoAppend("Loading " + file.name);
135
+ };
136
+ reader.onerror = function() {
137
+ infoAppend("Loading " + file.name + "failed");
138
+ }
139
+ reader.onload = function (e) {
140
+ infoAppend(file.name + " loaded");
141
+ v[0] = e.target.result;
142
+ callback();
143
+ };
144
+ try {
145
+ reader.readAsText(file);
146
+ } catch (e) {
147
+ infoAppend("Loading failed");
148
+ console.error(e.message);
149
+ }
150
+ }
151
+
152
+ let debugMode = false;
153
+ let chartLog;
154
+ let showChart = false;
155
+ let chartConainterDOM = document.getElementById("chart-container");
156
+ let showDistrictOverlayDom = document.getElementById("show-district-overlay");
157
+ let showGatewayOverlayDom = document.getElementById("show-gateway-overlay");
158
+
159
+ function parseDistrictMap() {
160
+ if (!districtDataRaw[0]) {
161
+ districtMap = null;
162
+ return;
163
+ }
164
+ try {
165
+ districtMap = JSON.parse(districtDataRaw[0]);
166
+ } catch (e) {
167
+ districtMap = null;
168
+ infoAppend("Parsing district map file failed");
169
+ }
170
+ }
171
+
172
+ function uploadOptionalDistrictThenChart(done) {
173
+ if (DistrictFileDom.value) {
174
+ uploadFile(districtDataRaw, DistrictFileDom.files[0], function() {
175
+ parseDistrictMap();
176
+ if (ChartFileDom.value) {
177
+ showChart = true;
178
+ uploadFile(chartData, ChartFileDom.files[0], done);
179
+ } else {
180
+ showChart = false;
181
+ done();
182
+ }
183
+ });
184
+ } else {
185
+ districtMap = null;
186
+ if (ChartFileDom.value) {
187
+ showChart = true;
188
+ uploadFile(chartData, ChartFileDom.files[0], done);
189
+ } else {
190
+ showChart = false;
191
+ done();
192
+ }
193
+ }
194
+ }
195
+
196
+ function start() {
197
+ if (loading) return;
198
+ loading = true;
199
+ infoReset();
200
+ showDistrictOverlay = showDistrictOverlayDom.checked;
201
+ showGatewayOverlay = showGatewayOverlayDom.checked;
202
+ uploadFile(roadnetData, RoadnetFileDom.files[0], function(){
203
+ uploadFile(replayData, ReplayFileDom.files[0], function(){
204
+ let after_update = function() {
205
+ infoAppend("drawing roadnet");
206
+ ready = false;
207
+ document.getElementById("guide").classList.add("d-none");
208
+ hideCanvas();
209
+ try {
210
+ simulation = JSON.parse(roadnetData[0]);
211
+ } catch (e) {
212
+ infoAppend("Parsing roadnet file failed");
213
+ loading = false;
214
+ return;
215
+ }
216
+ try {
217
+ logs = replayData[0].split('\n');
218
+ logs.pop();
219
+ } catch (e) {
220
+ infoAppend("Reading replay file failed");
221
+ loading = false;
222
+ return;
223
+ }
224
+
225
+ totalStep = logs.length;
226
+ if (showChart) {
227
+ chartConainterDOM.classList.remove("d-none");
228
+ let chart_lines = chartData[0].split('\n');
229
+ if (chart_lines.length == 0) {
230
+ infoAppend("Chart file is empty");
231
+ showChart = false;
232
+ }
233
+ chartLog = [];
234
+ for (let i = 0 ; i < totalStep ; ++i) {
235
+ step_data = chart_lines[i + 1].split(/[ \t]+/);
236
+ chartLog.push([]);
237
+ for (let j = 0; j < step_data.length; ++j) {
238
+ chartLog[i].push(parseFloat(step_data[j]));
239
+ }
240
+ }
241
+ chart.init(chart_lines[0], chartLog[0].length, totalStep);
242
+ }else {
243
+ chartConainterDOM.classList.add("d-none");
244
+ }
245
+
246
+ controls.paused = false;
247
+ cnt = 0;
248
+ debugMode = document.getElementById("debug-mode").checked;
249
+ setTimeout(function () {
250
+ try {
251
+ drawRoadnet();
252
+ } catch (e) {
253
+ infoAppend("Drawing roadnet failed");
254
+ console.error(e.message);
255
+ loading = false;
256
+ return;
257
+ }
258
+ ready = true;
259
+ loading = false;
260
+ infoAppend("Start replaying");
261
+ }, 200);
262
+ };
263
+
264
+ uploadOptionalDistrictThenChart(after_update);
265
+
266
+ }); // replay callback
267
+ }); // roadnet callback
268
+ }
269
+
270
+ let RoadnetFileDom = document.getElementById("roadnet-file");
271
+ let ReplayFileDom = document.getElementById("replay-file");
272
+ let ChartFileDom = document.getElementById("chart-file");
273
+ let DistrictFileDom = document.getElementById("district-file");
274
+
275
+ RoadnetFileDom.addEventListener("change",
276
+ handleChooseFile(roadnetData, document.getElementById("roadnet-label")), false);
277
+ ReplayFileDom.addEventListener("change",
278
+ handleChooseFile(replayData, document.getElementById("replay-label")), false);
279
+ ChartFileDom.addEventListener("change",
280
+ handleChooseFile(chartData, document.getElementById("chart-label")), false);
281
+ DistrictFileDom.addEventListener("change",
282
+ handleChooseFile(districtDataRaw, document.getElementById("district-label")), false);
283
+
284
+ showDistrictOverlayDom.addEventListener("change", function(evt) {
285
+ showDistrictOverlay = evt.target.checked;
286
+ if (ready) drawRoadnet();
287
+ });
288
+ showGatewayOverlayDom.addEventListener("change", function(evt) {
289
+ showGatewayOverlay = evt.target.checked;
290
+ if (ready) drawRoadnet();
291
+ });
292
+
293
+ document.getElementById("start-btn").addEventListener("click", start);
294
+
295
+ document.getElementById("slow-btn").addEventListener("click", function() {
296
+ updateReplaySpeed(controls.replaySpeed - 0.1);
297
+ })
298
+
299
+ document.getElementById("fast-btn").addEventListener("click", function() {
300
+ updateReplaySpeed(controls.replaySpeed + 0.1);
301
+ })
302
+
303
+ function updateReplaySpeed(speed){
304
+ speed = Math.min(speed, 1);
305
+ speed = Math.max(speed, 0);
306
+ controls.replaySpeed = speed;
307
+ replayControlDom.value = speed * 100;
308
+ replaySpeedDom.innerHTML = speed.toFixed(2);
309
+ }
310
+
311
+ updateReplaySpeed(0.5);
312
+
313
+ replayControlDom.addEventListener('change', function(e){
314
+ updateReplaySpeed(replayControlDom.value / 100);
315
+ });
316
+
317
+ document.addEventListener('keydown', function(e) {
318
+ if (e.keyCode == P) {
319
+ controls.paused = !controls.paused;
320
+ } else if (e.keyCode == ONE) {
321
+ updateReplaySpeed(Math.max(controls.replaySpeed / 1.5, controls.replaySpeedMin));
322
+ } else if (e.keyCode == TWO ) {
323
+ updateReplaySpeed(Math.min(controls.replaySpeed * 1.5, controls.replaySpeedMax));
324
+ } else if (e.keyCode == LEFT_BRACKET) {
325
+ cnt = (cnt - 1) % totalStep;
326
+ cnt = (cnt + totalStep) % totalStep;
327
+ drawStep(cnt);
328
+ } else if (e.keyCode == RIGHT_BRACKET) {
329
+ cnt = (cnt + 1) % totalStep;
330
+ drawStep(cnt);
331
+ } else {
332
+ keyDown.add(e.keyCode)
333
+ }
334
+ });
335
+
336
+ document.addEventListener('keyup', (e) => keyDown.delete(e.keyCode));
337
+
338
+ nodeCanvas.addEventListener('dblclick', function(e){
339
+ controls.paused = !controls.paused;
340
+ });
341
+
342
+ pauseButton.addEventListener('click', function(e){
343
+ controls.paused = !controls.paused;
344
+ });
345
+
346
+ function initCanvas() {
347
+ app = new Application({
348
+ width: nodeCanvas.offsetWidth,
349
+ height: nodeCanvas.offsetHeight,
350
+ transparent: false,
351
+ backgroundColor: BACKGROUND_COLOR
352
+ });
353
+
354
+ nodeCanvas.appendChild(app.view);
355
+ app.view.classList.add("d-none");
356
+
357
+ renderer = app.renderer;
358
+ renderer.interactive = true;
359
+ renderer.autoResize = true;
360
+
361
+ renderer.resize(nodeCanvas.offsetWidth, nodeCanvas.offsetHeight);
362
+ app.ticker.add(run);
363
+ }
364
+
365
+ function showCanvas() {
366
+ document.getElementById("spinner").classList.add("d-none");
367
+ app.view.classList.remove("d-none");
368
+ }
369
+
370
+ function hideCanvas() {
371
+ document.getElementById("spinner").classList.remove("d-none");
372
+ app.view.classList.add("d-none");
373
+ }
374
+
375
+ function drawRoadnet() {
376
+ if (simulatorContainer) {
377
+ simulatorContainer.destroy(true);
378
+ }
379
+ app.stage.removeChildren();
380
+ viewport = new Viewport.Viewport({
381
+ screenWidth: window.innerWidth,
382
+ screenHeight: window.innerHeight,
383
+ interaction: app.renderer.plugins.interaction
384
+ });
385
+ viewport
386
+ .drag()
387
+ .pinch()
388
+ .wheel()
389
+ .decelerate();
390
+ app.stage.addChild(viewport);
391
+ simulatorContainer = new Container();
392
+ viewport.addChild(simulatorContainer);
393
+
394
+ roadnet = simulation.static;
395
+ nodes = [];
396
+ edges = [];
397
+ trafficLightsG = {};
398
+
399
+ for (let i = 0, len = roadnet.nodes.length;i < len;++i) {
400
+ node = roadnet.nodes[i];
401
+ node.point = new Point(transCoord(node.point));
402
+ nodes[node.id] = node;
403
+ }
404
+
405
+ for (let i = 0, len = roadnet.edges.length;i < len;++i) {
406
+ edge = roadnet.edges[i];
407
+ edge.from = nodes[edge.from];
408
+ edge.to = nodes[edge.to];
409
+ for (let j = 0, len = edge.points.length;j < len;++j) {
410
+ edge.points[j] = new Point(transCoord(edge.points[j]));
411
+ }
412
+ edges[edge.id] = edge;
413
+ }
414
+
415
+ /**
416
+ * Draw Map
417
+ */
418
+ trafficLightContainer = new ParticleContainer(MAX_TRAFFIC_LIGHT_NUM, {tint: true});
419
+ let mapContainer, mapGraphics;
420
+ if (debugMode) {
421
+ mapContainer = new Container();
422
+ simulatorContainer.addChild(mapContainer);
423
+ }else {
424
+ mapGraphics = new Graphics();
425
+ simulatorContainer.addChild(mapGraphics);
426
+ }
427
+
428
+ for (nodeId in nodes) {
429
+ if (!nodes[nodeId].virtual) {
430
+ let nodeGraphics;
431
+ if (debugMode) {
432
+ nodeGraphics = new Graphics();
433
+ mapContainer.addChild(nodeGraphics);
434
+ } else {
435
+ nodeGraphics = mapGraphics;
436
+ }
437
+ drawNode(nodes[nodeId], nodeGraphics);
438
+ }
439
+ }
440
+ for (edgeId in edges) {
441
+ let edgeGraphics;
442
+ if (debugMode) {
443
+ edgeGraphics = new Graphics();
444
+ mapContainer.addChild(edgeGraphics);
445
+ } else {
446
+ edgeGraphics = mapGraphics;
447
+ }
448
+ drawEdge(edges[edgeId], edgeGraphics);
449
+ }
450
+ drawOverlayLayers();
451
+ let bounds = simulatorContainer.getBounds();
452
+ simulatorContainer.pivot.set(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
453
+ simulatorContainer.position.set(renderer.width / 2, renderer.height / 2);
454
+ simulatorContainer.addChild(trafficLightContainer);
455
+
456
+ /**
457
+ * Settings for Cars
458
+ */
459
+ TURN_SIGNAL_LENGTH = CAR_LENGTH;
460
+ TURN_SIGNAL_WIDTH = CAR_WIDTH / 2;
461
+
462
+ var carG = new Graphics();
463
+ carG.lineStyle(0);
464
+ carG.beginFill(0xFFFFFF, 0.8);
465
+ carG.drawRect(0, 0, CAR_LENGTH, CAR_WIDTH);
466
+
467
+ let carTexture = renderer.generateTexture(carG);
468
+
469
+ let signalG = new Graphics();
470
+ signalG.beginFill(TURN_SIGNAL_COLOR, 0.7).drawRect(0,0, TURN_SIGNAL_LENGTH, TURN_SIGNAL_WIDTH)
471
+ .drawRect(0, 3 * CAR_WIDTH - TURN_SIGNAL_WIDTH, TURN_SIGNAL_LENGTH, TURN_SIGNAL_WIDTH).endFill();
472
+ let turnSignalTexture = renderer.generateTexture(signalG);
473
+
474
+ let signalLeft = new Texture(turnSignalTexture, new Rectangle(0, 0, TURN_SIGNAL_LENGTH, CAR_WIDTH));
475
+ let signalStraight = new Texture(turnSignalTexture, new Rectangle(0, CAR_WIDTH, TURN_SIGNAL_LENGTH, CAR_WIDTH));
476
+ let signalRight = new Texture(turnSignalTexture, new Rectangle(0, CAR_WIDTH * 2, TURN_SIGNAL_LENGTH, CAR_WIDTH));
477
+ turnSignalTextures = [signalLeft, signalStraight, signalRight];
478
+
479
+
480
+ carPool = [];
481
+ if (debugMode)
482
+ carContainer = new Container();
483
+ else
484
+ carContainer = new ParticleContainer(NUM_CAR_POOL, {rotation: true, tint: true});
485
+
486
+
487
+ turnSignalContainer = new ParticleContainer(NUM_CAR_POOL, {rotation: true, tint: true});
488
+ simulatorContainer.addChild(carContainer);
489
+ simulatorContainer.addChild(turnSignalContainer);
490
+ for (let i = 0, len = NUM_CAR_POOL;i < len;++i) {
491
+ //var car = Sprite.fromImage("images/car.png")
492
+ let car = new Sprite(carTexture);
493
+ let signal = new Sprite(turnSignalTextures[1]);
494
+ car.anchor.set(1, 0.5);
495
+
496
+ if (debugMode) {
497
+ car.interactive = true;
498
+ car.on('mouseover', function () {
499
+ selectedDOM.innerText = car.name;
500
+ car.alpha = 0.8;
501
+ });
502
+ car.on('mouseout', function () {
503
+ // selectedDOM.innerText = "";
504
+ car.alpha = 1;
505
+ });
506
+ }
507
+ signal.anchor.set(1, 0.5);
508
+ carPool.push([car, signal]);
509
+ }
510
+ showCanvas();
511
+
512
+ return true;
513
+ }
514
+
515
+ function appendText(id, text) {
516
+ let p = document.createElement("span");
517
+ p.innerText = text;
518
+ document.getElementById("info").appendChild(p);
519
+ document.getElementById("info").appendChild(document.createElement("br"));
520
+ }
521
+
522
+ var statsFile = "";
523
+ var withRange = false;
524
+ var nodeStats, nodeRange;
525
+
526
+ initCanvas();
527
+
528
+
529
+ function transCoord(point) {
530
+ return [point[0], -point[1]];
531
+ }
532
+
533
+ function getDistrictColor(districtId) {
534
+ return DISTRICT_PALETTE[stringHash(districtId) % DISTRICT_PALETTE.length];
535
+ }
536
+
537
+ function cross2D(o, a, b) {
538
+ return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
539
+ }
540
+
541
+ function convexHull(points) {
542
+ if (!points || points.length <= 2) return points ? points.slice() : [];
543
+ let sorted = points
544
+ .slice()
545
+ .sort((p, q) => (p.x === q.x ? p.y - q.y : p.x - q.x));
546
+ let lower = [];
547
+ for (let i = 0; i < sorted.length; ++i) {
548
+ while (lower.length >= 2 && cross2D(lower[lower.length - 2], lower[lower.length - 1], sorted[i]) <= 0) {
549
+ lower.pop();
550
+ }
551
+ lower.push(sorted[i]);
552
+ }
553
+ let upper = [];
554
+ for (let i = sorted.length - 1; i >= 0; --i) {
555
+ while (upper.length >= 2 && cross2D(upper[upper.length - 2], upper[upper.length - 1], sorted[i]) <= 0) {
556
+ upper.pop();
557
+ }
558
+ upper.push(sorted[i]);
559
+ }
560
+ lower.pop();
561
+ upper.pop();
562
+ return lower.concat(upper);
563
+ }
564
+
565
+ function drawDistrictRegionFills(graphics, districtToPoints) {
566
+ for (let districtId in districtToPoints) {
567
+ let pts = districtToPoints[districtId];
568
+ if (!pts || pts.length === 0) continue;
569
+ let color = getDistrictColor(districtId);
570
+ if (pts.length >= 3) {
571
+ let hull = convexHull(pts);
572
+ if (hull.length >= 3) {
573
+ graphics.lineStyle(1.2, color, 0.35);
574
+ graphics.beginFill(color, 0.11);
575
+ graphics.moveTo(hull[0].x, hull[0].y);
576
+ for (let i = 1; i < hull.length; ++i) {
577
+ graphics.lineTo(hull[i].x, hull[i].y);
578
+ }
579
+ graphics.lineTo(hull[0].x, hull[0].y);
580
+ graphics.endFill();
581
+ continue;
582
+ }
583
+ }
584
+ if (pts.length === 2) {
585
+ graphics.lineStyle(18, color, 0.10);
586
+ graphics.drawLine(pts[0], pts[1]);
587
+ continue;
588
+ }
589
+ graphics.lineStyle(0);
590
+ graphics.beginFill(color, 0.12);
591
+ graphics.drawCircle(pts[0].x, pts[0].y, 18);
592
+ graphics.endFill();
593
+ }
594
+ }
595
+
596
+ function drawEdgePolyline(graphics, edge, width, color, alpha) {
597
+ graphics.lineStyle(width, color, alpha);
598
+ for (let i = 1; i < edge.points.length; ++i) {
599
+ graphics.drawLine(edge.points[i - 1], edge.points[i]);
600
+ }
601
+ }
602
+
603
+ function drawOverlayLayers() {
604
+ if (overlayContainer) {
605
+ overlayContainer.destroy(true);
606
+ overlayContainer = null;
607
+ }
608
+ if (!showDistrictOverlay && !showGatewayOverlay) {
609
+ return;
610
+ }
611
+ overlayContainer = new Container();
612
+ simulatorContainer.addChild(overlayContainer);
613
+
614
+ let overlayGraphics = new Graphics();
615
+ overlayContainer.addChild(overlayGraphics);
616
+
617
+ let intersectionToDistrict = {};
618
+ if (districtMap && districtMap.intersection_to_district) {
619
+ intersectionToDistrict = districtMap.intersection_to_district;
620
+ }
621
+ let gatewayNodeIds = new Set(
622
+ districtMap && districtMap.gateway_intersections
623
+ ? districtMap.gateway_intersections
624
+ : []
625
+ );
626
+ let gatewayEdgeIds = new Set(
627
+ districtMap && districtMap.gateway_roads
628
+ ? districtMap.gateway_roads
629
+ : []
630
+ );
631
+ let nodeDegree = {};
632
+ for (let edgeId in edges) {
633
+ let edge = edges[edgeId];
634
+ nodeDegree[edge.from.id] = (nodeDegree[edge.from.id] || 0) + 1;
635
+ nodeDegree[edge.to.id] = (nodeDegree[edge.to.id] || 0) + 1;
636
+ }
637
+ for (let nodeId in nodes) {
638
+ if ((nodeDegree[nodeId] || 0) <= 1) {
639
+ gatewayNodeIds.add(nodeId);
640
+ }
641
+ }
642
+
643
+ if (showDistrictOverlay && districtMap && districtMap.intersection_to_district) {
644
+ let districtToPoints = {};
645
+ for (let nodeId in nodes) {
646
+ let districtId = intersectionToDistrict[nodeId];
647
+ if (!districtId) continue;
648
+ if (!districtToPoints[districtId]) districtToPoints[districtId] = [];
649
+ districtToPoints[districtId].push(nodes[nodeId].point);
650
+ }
651
+ drawDistrictRegionFills(overlayGraphics, districtToPoints);
652
+
653
+ for (let edgeId in edges) {
654
+ let edge = edges[edgeId];
655
+ let fromDistrict = intersectionToDistrict[edge.from.id];
656
+ let toDistrict = intersectionToDistrict[edge.to.id];
657
+ if (!fromDistrict || !toDistrict) continue;
658
+ if (fromDistrict === toDistrict) {
659
+ drawEdgePolyline(
660
+ overlayGraphics,
661
+ edge,
662
+ 1.2,
663
+ getDistrictColor(fromDistrict),
664
+ 0.45
665
+ );
666
+ } else {
667
+ drawEdgePolyline(
668
+ overlayGraphics,
669
+ edge,
670
+ 1.8,
671
+ DISTRICT_BORDER_COLOR,
672
+ 0.8
673
+ );
674
+ }
675
+ }
676
+ for (let nodeId in nodes) {
677
+ let districtId = intersectionToDistrict[nodeId];
678
+ if (!districtId) continue;
679
+ let point = nodes[nodeId].point;
680
+ overlayGraphics.beginFill(getDistrictColor(districtId), 0.55);
681
+ overlayGraphics.drawCircle(point.x, point.y, 2.0);
682
+ overlayGraphics.endFill();
683
+ }
684
+ }
685
+
686
+ if (showGatewayOverlay) {
687
+ for (let edgeId in edges) {
688
+ let edge = edges[edgeId];
689
+ let fromGateway = gatewayNodeIds.has(edge.from.id) || String(edge.from.id).indexOf("g_") === 0;
690
+ let toGateway = gatewayNodeIds.has(edge.to.id) || String(edge.to.id).indexOf("g_") === 0;
691
+ let edgeLooksGateway = edgeId.indexOf("_g_") >= 0 || edgeId.indexOf("r_g_") === 0;
692
+ if (
693
+ gatewayEdgeIds.has(edgeId)
694
+ || fromGateway
695
+ || toGateway
696
+ || edgeLooksGateway
697
+ ) {
698
+ drawEdgePolyline(
699
+ overlayGraphics,
700
+ edge,
701
+ 5.6,
702
+ GATEWAY_EDGE_COLOR,
703
+ 0.82
704
+ );
705
+ }
706
+ }
707
+ for (let nodeId in nodes) {
708
+ if (gatewayNodeIds.has(nodeId) || String(nodeId).indexOf("g_") === 0) {
709
+ let point = nodes[nodeId].point;
710
+ overlayGraphics.lineStyle(0);
711
+ overlayGraphics.beginFill(GATEWAY_NODE_COLOR, 0.28);
712
+ overlayGraphics.drawCircle(point.x, point.y, 34.0);
713
+ overlayGraphics.endFill();
714
+ overlayGraphics.lineStyle(2.2, 0x1f2d3d, 0.98);
715
+ overlayGraphics.beginFill(GATEWAY_NODE_COLOR, 0.96);
716
+ overlayGraphics.drawCircle(point.x, point.y, 13.0);
717
+ overlayGraphics.endFill();
718
+ overlayGraphics.lineStyle(0);
719
+ overlayGraphics.beginFill(0xffffff, 0.92);
720
+ overlayGraphics.drawCircle(point.x, point.y, 5.2);
721
+ overlayGraphics.endFill();
722
+ }
723
+ }
724
+ }
725
+ }
726
+
727
+ PIXI.Graphics.prototype.drawLine = function(pointA, pointB) {
728
+ this.moveTo(pointA.x, pointA.y);
729
+ this.lineTo(pointB.x, pointB.y);
730
+ }
731
+
732
+ PIXI.Graphics.prototype.drawDashLine = function(pointA, pointB, dash = 16, gap = 8) {
733
+ let direct = pointA.directTo(pointB);
734
+ let distance = pointA.distanceTo(pointB);
735
+
736
+ let currentPoint = pointA;
737
+ let currentDistance = 0;
738
+ let length;
739
+ let finish = false;
740
+ while (true) {
741
+ this.moveTo(currentPoint.x, currentPoint.y);
742
+ if (currentDistance + dash >= distance) {
743
+ length = distance - currentDistance;
744
+ finish = true;
745
+ } else {
746
+ length = dash
747
+ }
748
+ currentPoint = currentPoint.moveAlong(direct, length);
749
+ this.lineTo(currentPoint.x, currentPoint.y);
750
+ if (finish) break;
751
+ currentDistance += length;
752
+
753
+ if (currentDistance + gap >= distance) {
754
+ break;
755
+ } else {
756
+ currentPoint = currentPoint.moveAlong(direct, gap);
757
+ currentDistance += gap;
758
+ }
759
+ }
760
+ };
761
+
762
+ function drawNode(node, graphics) {
763
+ graphics.beginFill(LANE_COLOR);
764
+ let outline = node.outline;
765
+ for (let i = 0 ; i < outline.length ; i+=2) {
766
+ outline[i+1] = -outline[i+1];
767
+ if (i == 0)
768
+ graphics.moveTo(outline[i], outline[i+1]);
769
+ else
770
+ graphics.lineTo(outline[i], outline[i+1]);
771
+ }
772
+ graphics.endFill();
773
+
774
+ if (debugMode) {
775
+ graphics.hitArea = new PIXI.Polygon(outline);
776
+ graphics.interactive = true;
777
+ graphics.on("mouseover", function () {
778
+ selectedDOM.innerText = node.id;
779
+ graphics.alpha = 0.5;
780
+ });
781
+ graphics.on("mouseout", function () {
782
+ graphics.alpha = 1;
783
+ });
784
+ }
785
+
786
+ }
787
+
788
+ function drawEdge(edge, graphics) {
789
+ let from = edge.from;
790
+ let to = edge.to;
791
+ let points = edge.points;
792
+
793
+ let pointA, pointAOffset, pointB, pointBOffset;
794
+ let prevPointBOffset = null;
795
+
796
+ let roadWidth = 0;
797
+ edge.laneWidths.forEach(function(l){
798
+ roadWidth += l;
799
+ }, 0);
800
+
801
+ let coords = [], coords1 = [];
802
+
803
+ for (let i = 1;i < points.length;++i) {
804
+ if (i == 1){
805
+ pointA = points[0].moveAlongDirectTo(points[1], from.virtual ? 0 : from.width);
806
+ pointAOffset = points[0].directTo(points[1]).rotate(ROTATE);
807
+ } else {
808
+ pointA = points[i-1];
809
+ pointAOffset = prevPointBOffset;
810
+ }
811
+ if (i == points.length - 1) {
812
+ pointB = points[i].moveAlongDirectTo(points[i-1], to.virtual ? 0 : to.width);
813
+ pointBOffset = points[i-1].directTo(points[i]).rotate(ROTATE);
814
+ } else {
815
+ pointB = points[i];
816
+ pointBOffset = points[i-1].directTo(points[i+1]).rotate(ROTATE);
817
+ }
818
+ prevPointBOffset = pointBOffset;
819
+
820
+ lightG = new Graphics();
821
+ lightG.lineStyle(TRAFFIC_LIGHT_WIDTH, 0xFFFFFF);
822
+ lightG.drawLine(new Point(0, 0), new Point(1, 0));
823
+ lightTexture = renderer.generateTexture(lightG);
824
+
825
+ // Draw Traffic Lights
826
+ if (i == points.length-1 && !to.virtual) {
827
+ edgeTrafficLights = [];
828
+ prevOffset = offset = 0;
829
+ for (lane = 0;lane < edge.nLane;++lane) {
830
+ offset += edge.laneWidths[lane];
831
+ var light = new Sprite(lightTexture);
832
+ light.anchor.set(0, 0.5);
833
+ light.scale.set(offset - prevOffset, 1);
834
+ point_ = pointB.moveAlong(pointBOffset, prevOffset);
835
+ light.position.set(point_.x, point_.y);
836
+ light.rotation = pointBOffset.getAngleInRadians();
837
+ edgeTrafficLights.push(light);
838
+ prevOffset = offset;
839
+ trafficLightContainer.addChild(light);
840
+ }
841
+ trafficLightsG[edge.id] = edgeTrafficLights;
842
+ }
843
+
844
+ // Draw Roads
845
+ graphics.lineStyle(LANE_BORDER_WIDTH, LANE_BORDER_COLOR, 1);
846
+ graphics.drawLine(pointA, pointB);
847
+
848
+ pointA1 = pointA.moveAlong(pointAOffset, roadWidth);
849
+ pointB1 = pointB.moveAlong(pointBOffset, roadWidth);
850
+
851
+ graphics.lineStyle(0);
852
+ graphics.beginFill(LANE_COLOR);
853
+
854
+ coords = coords.concat([pointA.x, pointA.y, pointB.x, pointB.y]);
855
+ coords1 = coords1.concat([pointA1.y, pointA1.x, pointB1.y, pointB1.x]);
856
+
857
+ graphics.drawPolygon([pointA.x, pointA.y, pointB.x, pointB.y, pointB1.x, pointB1.y, pointA1.x, pointA1.y]);
858
+ graphics.endFill();
859
+
860
+ offset = 0;
861
+ for (let lane = 0, len = edge.nLane-1;lane < len;++lane) {
862
+ offset += edge.laneWidths[lane];
863
+ graphics.lineStyle(LANE_BORDER_WIDTH, LANE_INNER_COLOR);
864
+ graphics.drawDashLine(pointA.moveAlong(pointAOffset, offset), pointB.moveAlong(pointBOffset, offset), LANE_DASH, LANE_GAP);
865
+ }
866
+
867
+ offset += edge.laneWidths[edge.nLane-1];
868
+
869
+ // graphics.lineStyle(LANE_BORDER_WIDTH, LANE_BORDER_COLOR);
870
+ // graphics.drawLine(pointA.moveAlong(pointAOffset, offset), pointB.moveAlong(pointBOffset, offset));
871
+ }
872
+
873
+ if (debugMode) {
874
+ coords = coords.concat(coords1.reverse());
875
+ graphics.interactive = true;
876
+ graphics.hitArea = new PIXI.Polygon(coords);
877
+ graphics.on("mouseover", function () {
878
+ graphics.alpha = 0.5;
879
+ selectedDOM.innerText = edge.id;
880
+ });
881
+
882
+ graphics.on("mouseout", function () {
883
+ graphics.alpha = 1;
884
+ });
885
+ }
886
+ }
887
+
888
+ function run(delta) {
889
+ let redraw = false;
890
+
891
+ if (ready && (!controls.paused || redraw)) {
892
+ try {
893
+ drawStep(cnt);
894
+ }catch (e) {
895
+ infoAppend("Error occurred when drawing");
896
+ ready = false;
897
+ }
898
+ if (!controls.paused) {
899
+ frameElapsed += 1;
900
+ if (frameElapsed >= 1 / controls.replaySpeed ** 2) {
901
+ cnt += 1;
902
+ frameElapsed = 0;
903
+ if (cnt == totalStep) cnt = 0;
904
+ }
905
+ }
906
+ }
907
+ }
908
+
909
+ function _statusToColor(status) {
910
+ switch (status) {
911
+ case 'r':
912
+ return LIGHT_RED;
913
+ case 'g':
914
+ return LIGHT_GREEN;
915
+ default:
916
+ return 0x808080;
917
+ }
918
+ }
919
+
920
+ function stringHash(str) {
921
+ let hash = 0;
922
+ let p = 127, p_pow = 1;
923
+ let m = 1e9 + 9;
924
+ for (let i = 0; i < str.length; i++) {
925
+ hash = (hash + str.charCodeAt(i) * p_pow) % m;
926
+ p_pow = (p_pow * p) % m;
927
+ }
928
+ return hash;
929
+ }
930
+
931
+ function drawStep(step) {
932
+ if (showChart && (step > chart.ptr || step == 0)) {
933
+ if (step == 0) {
934
+ chart.clear();
935
+ }
936
+ chart.ptr = step;
937
+ chart.addData(chartLog[step]);
938
+ }
939
+
940
+ let [carLogs, tlLogs] = logs[step].split(';');
941
+
942
+ tlLogs = tlLogs.split(',');
943
+ carLogs = carLogs.split(',');
944
+
945
+ let tlLog, tlEdge, tlStatus;
946
+ for (let i = 0, len = tlLogs.length;i < len;++i) {
947
+ tlLog = tlLogs[i].split(' ');
948
+ tlEdge = tlLog[0];
949
+ tlStatus = tlLog.slice(1);
950
+ for (let j = 0, len = tlStatus.length;j < len;++j) {
951
+ trafficLightsG[tlEdge][j].tint = _statusToColor(tlStatus[j]);
952
+ if (tlStatus[j] == 'i' ) {
953
+ trafficLightsG[tlEdge][j].alpha = 0;
954
+ }else{
955
+ trafficLightsG[tlEdge][j].alpha = 1;
956
+ }
957
+ }
958
+ }
959
+
960
+ carContainer.removeChildren();
961
+ turnSignalContainer.removeChildren();
962
+ let carLog, position, length, width;
963
+ for (let i = 0, len = carLogs.length - 1;i < len;++i) {
964
+ carLog = carLogs[i].split(' ');
965
+ position = transCoord([parseFloat(carLog[0]), parseFloat(carLog[1])]);
966
+ length = parseFloat(carLog[5]);
967
+ width = parseFloat(carLog[6]);
968
+ carPool[i][0].position.set(position[0], position[1]);
969
+ carPool[i][0].rotation = 2*Math.PI - parseFloat(carLog[2]);
970
+ carPool[i][0].name = carLog[3];
971
+ let carColorId = stringHash(carLog[3]) % CAR_COLORS_NUM;
972
+ carPool[i][0].tint = CAR_COLORS[carColorId];
973
+ carPool[i][0].width = length;
974
+ carPool[i][0].height = width;
975
+ carContainer.addChild(carPool[i][0]);
976
+
977
+ let laneChange = parseInt(carLog[4]) + 1;
978
+ carPool[i][1].position.set(position[0], position[1]);
979
+ carPool[i][1].rotation = carPool[i][0].rotation;
980
+ carPool[i][1].texture = turnSignalTextures[laneChange];
981
+ carPool[i][1].width = length;
982
+ carPool[i][1].height = width;
983
+ turnSignalContainer.addChild(carPool[i][1]);
984
+ }
985
+ nodeCarNum.innerText = carLogs.length-1;
986
+ nodeTotalStep.innerText = totalStep;
987
+ nodeCurrentStep.innerText = cnt+1;
988
+ nodeProgressPercentage.innerText = (cnt / totalStep * 100).toFixed(2) + "%";
989
+ if (statsFile != "") {
990
+ if (withRange) nodeRange.value = stats[step][1];
991
+ nodeStats.innerText = stats[step][0].toFixed(2);
992
+ }
993
+ }
994
+
995
+ /*
996
+ Chart
997
+ */
998
+ let chart = {
999
+ max_steps: 3600,
1000
+ data: {
1001
+ labels: [],
1002
+ series: [[]]
1003
+ },
1004
+ options: {
1005
+ showPoint: false,
1006
+ lineSmooth: false,
1007
+ axisX: {
1008
+ showGrid: false,
1009
+ showLabel: false
1010
+ }
1011
+ },
1012
+ init : function(title, series_cnt, max_step){
1013
+ document.getElementById("chart-title").innerText = title;
1014
+ this.max_steps = max_step;
1015
+ this.data.labels = new Array(this.max_steps);
1016
+ this.data.series = [];
1017
+ for (let i = 0 ; i < series_cnt ; ++i)
1018
+ this.data.series.push([]);
1019
+ this.chart = new Chartist.Line('#chart', this.data, this.options);
1020
+ },
1021
+ addData: function (value) {
1022
+ for (let i = 0 ; i < value.length; ++i) {
1023
+ this.data.series[i].push(value[i]);
1024
+ if (this.data.series[i].length > this.max_steps) {
1025
+ this.data.series[i].shift();
1026
+ }
1027
+ }
1028
+ this.chart.update();
1029
+ },
1030
+ clear: function() {
1031
+ for (let i = 0 ; i < this.data.series.length ; ++i)
1032
+ this.data.series[i] = [];
1033
+ },
1034
+ ptr: 0
1035
+ };
third_party/CityFlow/frontend/script_multi.js CHANGED
The diff for this file is too large to render. See raw diff
 
third_party/CityFlow/frontend/spinner.css CHANGED
@@ -1,53 +1,53 @@
1
- /* https://tobiasahlin.com/spinkit/ */
2
- .spinner {
3
- margin: 100px auto;
4
- width: 50px;
5
- height: 40px;
6
- text-align: center;
7
- font-size: 10px;
8
- }
9
-
10
- .spinner > div {
11
- background-color: #333;
12
- height: 100%;
13
- width: 6px;
14
- display: inline-block;
15
-
16
- -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
17
- animation: sk-stretchdelay 1.2s infinite ease-in-out;
18
- }
19
-
20
- .spinner .rect2 {
21
- -webkit-animation-delay: -1.1s;
22
- animation-delay: -1.1s;
23
- }
24
-
25
- .spinner .rect3 {
26
- -webkit-animation-delay: -1.0s;
27
- animation-delay: -1.0s;
28
- }
29
-
30
- .spinner .rect4 {
31
- -webkit-animation-delay: -0.9s;
32
- animation-delay: -0.9s;
33
- }
34
-
35
- .spinner .rect5 {
36
- -webkit-animation-delay: -0.8s;
37
- animation-delay: -0.8s;
38
- }
39
-
40
- @-webkit-keyframes sk-stretchdelay {
41
- 0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
42
- 20% { -webkit-transform: scaleY(1.0) }
43
- }
44
-
45
- @keyframes sk-stretchdelay {
46
- 0%, 40%, 100% {
47
- transform: scaleY(0.4);
48
- -webkit-transform: scaleY(0.4);
49
- } 20% {
50
- transform: scaleY(1.0);
51
- -webkit-transform: scaleY(1.0);
52
- }
53
  }
 
1
+ /* https://tobiasahlin.com/spinkit/ */
2
+ .spinner {
3
+ margin: 100px auto;
4
+ width: 50px;
5
+ height: 40px;
6
+ text-align: center;
7
+ font-size: 10px;
8
+ }
9
+
10
+ .spinner > div {
11
+ background-color: #333;
12
+ height: 100%;
13
+ width: 6px;
14
+ display: inline-block;
15
+
16
+ -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
17
+ animation: sk-stretchdelay 1.2s infinite ease-in-out;
18
+ }
19
+
20
+ .spinner .rect2 {
21
+ -webkit-animation-delay: -1.1s;
22
+ animation-delay: -1.1s;
23
+ }
24
+
25
+ .spinner .rect3 {
26
+ -webkit-animation-delay: -1.0s;
27
+ animation-delay: -1.0s;
28
+ }
29
+
30
+ .spinner .rect4 {
31
+ -webkit-animation-delay: -0.9s;
32
+ animation-delay: -0.9s;
33
+ }
34
+
35
+ .spinner .rect5 {
36
+ -webkit-animation-delay: -0.8s;
37
+ animation-delay: -0.8s;
38
+ }
39
+
40
+ @-webkit-keyframes sk-stretchdelay {
41
+ 0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
42
+ 20% { -webkit-transform: scaleY(1.0) }
43
+ }
44
+
45
+ @keyframes sk-stretchdelay {
46
+ 0%, 40%, 100% {
47
+ transform: scaleY(0.4);
48
+ -webkit-transform: scaleY(0.4);
49
+ } 20% {
50
+ transform: scaleY(1.0);
51
+ -webkit-transform: scaleY(1.0);
52
+ }
53
  }
third_party/CityFlow/frontend/style.css CHANGED
@@ -1,64 +1,64 @@
1
- html, body {
2
- margin: 0;
3
- padding: 0;
4
- height: 100%;
5
- width: 100%;
6
- overflow: hidden;
7
- }
8
-
9
- .noselect {
10
- -webkit-touch-callout: none;
11
- -webkit-user-select: none;
12
- -khtml-user-select: none;
13
- -moz-user-select: none;
14
- -ms-user-select: none;
15
- user-select: none;
16
- }
17
-
18
- #simulator-canvas {
19
- marigin: 0;
20
- padding: 0;
21
- height: 100%;
22
- width: 100%;
23
- overflow: hidden;
24
- }
25
-
26
- .status-panel {
27
- z-index: 3;
28
- height: 100vh;
29
- overflow-y: auto;
30
- }
31
-
32
- #info {
33
- max-height: 100%;
34
- }
35
-
36
- #chart {
37
- height: 100%;
38
- }
39
-
40
- #chart .ct-line {
41
- stroke-width: 1px;
42
- stroke-opacity: 0.8;
43
- }
44
-
45
- #chart .ct-label {
46
- font-weight: bold;
47
- }
48
-
49
- #chart-container {
50
- background-color: rgba(216, 220, 225, 0.8);
51
- position: absolute;
52
- bottom: 0;
53
- right: 0;
54
- left: 0;
55
- box-sizing: border-box;
56
- }
57
-
58
- #chart-title {
59
- font-weight: bold;
60
- font-size: large;
61
- color: #6c757d;
62
- width: 100%;
63
- text-align: center;
64
  }
 
1
+ html, body {
2
+ margin: 0;
3
+ padding: 0;
4
+ height: 100%;
5
+ width: 100%;
6
+ overflow: hidden;
7
+ }
8
+
9
+ .noselect {
10
+ -webkit-touch-callout: none;
11
+ -webkit-user-select: none;
12
+ -khtml-user-select: none;
13
+ -moz-user-select: none;
14
+ -ms-user-select: none;
15
+ user-select: none;
16
+ }
17
+
18
+ #simulator-canvas {
19
+ marigin: 0;
20
+ padding: 0;
21
+ height: 100%;
22
+ width: 100%;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .status-panel {
27
+ z-index: 3;
28
+ height: 100vh;
29
+ overflow-y: auto;
30
+ }
31
+
32
+ #info {
33
+ max-height: 100%;
34
+ }
35
+
36
+ #chart {
37
+ height: 100%;
38
+ }
39
+
40
+ #chart .ct-line {
41
+ stroke-width: 1px;
42
+ stroke-opacity: 0.8;
43
+ }
44
+
45
+ #chart .ct-label {
46
+ font-weight: bold;
47
+ }
48
+
49
+ #chart-container {
50
+ background-color: rgba(216, 220, 225, 0.8);
51
+ position: absolute;
52
+ bottom: 0;
53
+ right: 0;
54
+ left: 0;
55
+ box-sizing: border-box;
56
+ }
57
+
58
+ #chart-title {
59
+ font-weight: bold;
60
+ font-size: large;
61
+ color: #6c757d;
62
+ width: 100%;
63
+ text-align: center;
64
  }
third_party/CityFlow/frontend/style_multi.css CHANGED
@@ -1,747 +1,747 @@
1
- /* =========================================================
2
- Traffic Visualizer – Multi-Panel Dashboard Styles
3
- ========================================================= */
4
-
5
- *, *::before, *::after { box-sizing: border-box; }
6
-
7
- body {
8
- margin: 0;
9
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
10
- background: #1a1d23;
11
- color: #d4d8e0;
12
- height: 100vh;
13
- overflow: hidden;
14
- display: flex;
15
- }
16
-
17
- /* ── Sidebar ──────────────────────────────────────────── */
18
- #sidebar {
19
- width: 270px;
20
- min-width: 180px;
21
- background: #22252e;
22
- border-right: 1px solid #333744;
23
- display: flex;
24
- flex-direction: column;
25
- overflow-y: auto;
26
- padding: 16px;
27
- gap: 16px;
28
- z-index: 10;
29
- scrollbar-width: thin;
30
- scrollbar-color: #3a4055 transparent;
31
- }
32
- #sidebar::-webkit-scrollbar { width: 5px; }
33
- #sidebar::-webkit-scrollbar-track { background: transparent; }
34
- #sidebar::-webkit-scrollbar-thumb { background: #3a4055; border-radius: 3px; }
35
- #sidebar::-webkit-scrollbar-thumb:hover { background: #5b6cf9; }
36
-
37
- #sidebar h1 {
38
- font-size: 18px;
39
- font-weight: 700;
40
- color: #fff;
41
- margin: 0 0 4px 0;
42
- letter-spacing: 0.5px;
43
- }
44
-
45
- #sidebar .subtitle {
46
- font-size: 11px;
47
- color: #777e90;
48
- margin: 0;
49
- }
50
-
51
- .sidebar-section {
52
- background: #1a1d23;
53
- border-radius: 8px;
54
- padding: 12px;
55
- border: 1px solid #2e3240;
56
- }
57
-
58
- .sidebar-section h3 {
59
- font-size: 11px;
60
- font-weight: 600;
61
- text-transform: uppercase;
62
- letter-spacing: 1px;
63
- color: #777e90;
64
- margin: 0 0 10px 0;
65
- }
66
-
67
- /* File upload zone */
68
- #roadnet-drop-zone {
69
- border: 2px dashed #3a4055;
70
- border-radius: 6px;
71
- padding: 20px 12px;
72
- text-align: center;
73
- cursor: pointer;
74
- transition: border-color 0.2s, background 0.2s;
75
- font-size: 13px;
76
- color: #777e90;
77
- }
78
- #roadnet-drop-zone:hover,
79
- #roadnet-drop-zone.drag-over {
80
- border-color: #5b6cf9;
81
- background: rgba(91, 108, 249, 0.06);
82
- color: #aab0c2;
83
- }
84
- #roadnet-drop-zone .drop-icon {
85
- font-size: 24px;
86
- margin-bottom: 6px;
87
- display: block;
88
- }
89
- #roadnet-drop-zone .drop-filename {
90
- font-size: 12px;
91
- color: #5b6cf9;
92
- font-weight: 600;
93
- margin-top: 4px;
94
- }
95
- #roadnet-file-input { display: none; }
96
-
97
- /* City/Scenario pickers */
98
- .picker-row {
99
- display: flex;
100
- flex-direction: column;
101
- gap: 6px;
102
- }
103
- .picker-row label {
104
- font-size: 11px;
105
- color: #777e90;
106
- font-weight: 600;
107
- text-transform: uppercase;
108
- letter-spacing: 0.5px;
109
- }
110
- .picker-row select {
111
- width: 100%;
112
- background: #22252e;
113
- border: 1px solid #3a4055;
114
- color: #d4d8e0;
115
- border-radius: 5px;
116
- padding: 6px 8px;
117
- font-size: 13px;
118
- outline: none;
119
- cursor: pointer;
120
- }
121
- .picker-row select:focus {
122
- border-color: #5b6cf9;
123
- }
124
- .picker-row select:disabled {
125
- opacity: 0.4;
126
- cursor: not-allowed;
127
- }
128
-
129
- /* Policy checkboxes */
130
- .policy-list {
131
- display: flex;
132
- flex-direction: column;
133
- gap: 7px;
134
- }
135
- .policy-item {
136
- display: flex;
137
- align-items: center;
138
- gap: 8px;
139
- cursor: pointer;
140
- user-select: none;
141
- }
142
- .policy-item input[type="checkbox"] {
143
- accent-color: var(--policy-color);
144
- width: 15px;
145
- height: 15px;
146
- cursor: pointer;
147
- }
148
- .policy-item.disabled {
149
- opacity: 0.4;
150
- cursor: not-allowed;
151
- }
152
- .policy-item.disabled input { cursor: not-allowed; }
153
-
154
- .policy-badge {
155
- display: inline-block;
156
- width: 10px;
157
- height: 10px;
158
- border-radius: 50%;
159
- flex-shrink: 0;
160
- }
161
- .policy-label {
162
- font-size: 13px;
163
- flex: 1;
164
- }
165
- .policy-tag {
166
- font-size: 10px;
167
- background: rgba(255,255,255,0.07);
168
- color: #888;
169
- border-radius: 3px;
170
- padding: 1px 5px;
171
- }
172
-
173
- .recent-runs-list {
174
- display: flex;
175
- flex-direction: column;
176
- gap: 8px;
177
- }
178
- .recent-run-empty {
179
- font-size: 12px;
180
- color: #777e90;
181
- }
182
- .recent-run-card {
183
- border: 1px solid #323747;
184
- border-radius: 7px;
185
- background: #22252e;
186
- padding: 9px 10px;
187
- display: flex;
188
- flex-direction: column;
189
- gap: 6px;
190
- }
191
- .recent-run-top {
192
- display: flex;
193
- justify-content: space-between;
194
- gap: 8px;
195
- align-items: baseline;
196
- }
197
- .recent-run-title {
198
- font-size: 12px;
199
- font-weight: 700;
200
- color: #e7ecf4;
201
- }
202
- .recent-run-time {
203
- font-size: 10px;
204
- color: #7f8a9d;
205
- white-space: nowrap;
206
- }
207
- .recent-run-meta {
208
- font-size: 11px;
209
- color: #aab4c4;
210
- }
211
- .recent-run-policies {
212
- display: flex;
213
- flex-wrap: wrap;
214
- gap: 4px;
215
- }
216
- .recent-run-chip {
217
- font-size: 10px;
218
- color: #dbe2ee;
219
- background: rgba(255,255,255,0.08);
220
- border: 1px solid rgba(255,255,255,0.08);
221
- border-radius: 999px;
222
- padding: 2px 6px;
223
- }
224
-
225
- .debug-log {
226
- max-height: 180px;
227
- overflow: auto;
228
- padding: 10px;
229
- border-radius: 8px;
230
- background: #161a22;
231
- border: 1px solid #2c3442;
232
- color: #dbe4ff;
233
- font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
234
- white-space: pre-wrap;
235
- }
236
- .recent-run-actions {
237
- display: flex;
238
- gap: 6px;
239
- }
240
- .recent-run-btn {
241
- flex: 1;
242
- background: #2b3040;
243
- border: 1px solid #40475d;
244
- color: #dce3ef;
245
- border-radius: 5px;
246
- padding: 5px 7px;
247
- font-size: 11px;
248
- cursor: pointer;
249
- }
250
- .recent-run-btn:hover {
251
- border-color: #5b6cf9;
252
- }
253
-
254
- /* View mode selector */
255
- .view-mode-row {
256
- display: flex;
257
- gap: 6px;
258
- }
259
- .view-mode-btn {
260
- flex: 1;
261
- background: #22252e;
262
- border: 1px solid #3a4055;
263
- color: #777e90;
264
- border-radius: 5px;
265
- padding: 6px 0;
266
- font-size: 12px;
267
- font-weight: 600;
268
- cursor: pointer;
269
- transition: all 0.15s;
270
- text-align: center;
271
- }
272
- .view-mode-btn:hover { border-color: #5b6cf9; color: #aab0c2; }
273
- .view-mode-btn.active {
274
- background: #5b6cf9;
275
- border-color: #5b6cf9;
276
- color: #fff;
277
- }
278
-
279
- /* Run button */
280
- #run-btn {
281
- width: 100%;
282
- padding: 10px;
283
- background: linear-gradient(135deg, #5b6cf9, #7c3aed);
284
- color: #fff;
285
- border: none;
286
- border-radius: 6px;
287
- font-size: 14px;
288
- font-weight: 600;
289
- cursor: pointer;
290
- transition: opacity 0.2s, transform 0.1s;
291
- letter-spacing: 0.3px;
292
- }
293
- #run-btn:hover { opacity: 0.9; }
294
- #run-btn:active { transform: scale(0.98); }
295
- #run-btn:disabled { opacity: 0.4; cursor: not-allowed; }
296
-
297
- /* Force-rerun checkbox */
298
- .force-rerun-row {
299
- display: flex;
300
- align-items: center;
301
- gap: 7px;
302
- font-size: 11px;
303
- color: #777e90;
304
- cursor: pointer;
305
- user-select: none;
306
- margin-top: 2px;
307
- }
308
- .force-rerun-row input[type="checkbox"] {
309
- accent-color: #5b6cf9;
310
- width: 13px;
311
- height: 13px;
312
- cursor: pointer;
313
- }
314
-
315
- /* Progress */
316
- #progress-section { display: none; }
317
-
318
- .progress-policy-row {
319
- padding: 6px 0;
320
- border-bottom: 1px solid #2e3240;
321
- }
322
- .progress-policy-row:last-child { border-bottom: none; }
323
-
324
- .progress-policy-top {
325
- display: flex;
326
- align-items: center;
327
- gap: 8px;
328
- margin-bottom: 5px;
329
- }
330
- .progress-policy-dot {
331
- width: 8px;
332
- height: 8px;
333
- border-radius: 50%;
334
- flex-shrink: 0;
335
- }
336
- .progress-policy-name { flex: 1; font-size: 12px; color: #d4d8e0; }
337
- .progress-policy-pct {
338
- font-size: 11px;
339
- font-variant-numeric: tabular-nums;
340
- color: #777e90;
341
- min-width: 36px;
342
- text-align: right;
343
- }
344
- .progress-policy-pct.done { color: #10b981; }
345
- .progress-policy-pct.error { color: #ef4444; }
346
-
347
- .progress-policy-bar-track {
348
- height: 4px;
349
- background: #2e3240;
350
- border-radius: 2px;
351
- overflow: hidden;
352
- }
353
- .progress-policy-bar-fill {
354
- height: 100%;
355
- border-radius: 2px;
356
- width: 0%;
357
- transition: width 0.15s ease;
358
- }
359
-
360
- /* Sidebar resize handle */
361
- #sidebar-resize-handle {
362
- width: 4px;
363
- min-width: 4px;
364
- background: #2e3240;
365
- cursor: col-resize;
366
- transition: background 0.15s;
367
- z-index: 10;
368
- flex-shrink: 0;
369
- }
370
- #sidebar-resize-handle:hover,
371
- #sidebar-resize-handle.dragging {
372
- background: #5b6cf9;
373
- }
374
-
375
- /* ── Main area ────────────────────────────────────────── */
376
- #main {
377
- flex: 1;
378
- display: flex;
379
- flex-direction: column;
380
- overflow: hidden;
381
- }
382
-
383
- /* Playback toolbar */
384
- #playback-bar {
385
- background: #22252e;
386
- border-bottom: 1px solid #333744;
387
- padding: 8px 16px;
388
- display: flex;
389
- align-items: center;
390
- gap: 12px;
391
- flex-shrink: 0;
392
- }
393
- #playback-bar button {
394
- background: #2e3240;
395
- border: 1px solid #3a4055;
396
- color: #d4d8e0;
397
- border-radius: 5px;
398
- padding: 5px 14px;
399
- font-size: 13px;
400
- cursor: pointer;
401
- transition: background 0.15s;
402
- }
403
- #playback-bar button:hover { background: #3a4055; }
404
- #playback-bar button:disabled { opacity: 0.4; cursor: not-allowed; }
405
-
406
- #summary-bar {
407
- background: #1c2028;
408
- border-bottom: 1px solid #2c3341;
409
- padding: 10px 12px;
410
- display: flex;
411
- gap: 10px;
412
- overflow-x: auto;
413
- flex-shrink: 0;
414
- }
415
-
416
- .summary-empty {
417
- color: #8d97aa;
418
- font-size: 13px;
419
- }
420
-
421
- .summary-card {
422
- min-width: 245px;
423
- background: #222733;
424
- border: 1px solid #313949;
425
- border-top: 3px solid var(--summary-color, #5b6cf9);
426
- border-radius: 10px;
427
- padding: 10px 12px;
428
- display: flex;
429
- flex-direction: column;
430
- gap: 8px;
431
- }
432
-
433
- .summary-card-header {
434
- display: flex;
435
- justify-content: space-between;
436
- align-items: baseline;
437
- gap: 8px;
438
- }
439
-
440
- .summary-card-title {
441
- font-size: 13px;
442
- font-weight: 700;
443
- color: #edf2ff;
444
- }
445
-
446
- .summary-card-subtitle {
447
- font-size: 10px;
448
- color: #9aa4b8;
449
- text-transform: uppercase;
450
- letter-spacing: 0.08em;
451
- }
452
-
453
- .summary-grid {
454
- display: grid;
455
- grid-template-columns: 1fr 1fr;
456
- gap: 6px 10px;
457
- }
458
-
459
- .summary-stat-label {
460
- font-size: 10px;
461
- color: #8893a8;
462
- text-transform: uppercase;
463
- letter-spacing: 0.08em;
464
- }
465
-
466
- .summary-stat-value {
467
- font-size: 13px;
468
- color: #f5f7fb;
469
- font-weight: 600;
470
- }
471
-
472
- .summary-delta {
473
- font-size: 11px;
474
- font-weight: 600;
475
- }
476
-
477
- .summary-delta.good { color: #34d399; }
478
- .summary-delta.bad { color: #f87171; }
479
- .summary-delta.neutral { color: #94a3b8; }
480
-
481
- #scrubber {
482
- flex: 1;
483
- accent-color: #5b6cf9;
484
- cursor: pointer;
485
- }
486
- #step-display {
487
- font-size: 12px;
488
- color: #777e90;
489
- font-variant-numeric: tabular-nums;
490
- min-width: 90px;
491
- text-align: right;
492
- }
493
- #speed-label {
494
- font-size: 12px;
495
- color: #777e90;
496
- min-width: 48px;
497
- }
498
-
499
- /* Panel grid */
500
- #panels-container {
501
- flex: 1;
502
- overflow: hidden;
503
- display: grid;
504
- gap: 2px;
505
- background: #111318;
506
- }
507
-
508
- /* Grid layouts */
509
- #panels-container.layout-1 {
510
- grid-template-columns: 1fr;
511
- grid-template-rows: 1fr;
512
- }
513
- #panels-container.layout-2 {
514
- grid-template-columns: 1fr 1fr;
515
- grid-template-rows: 1fr;
516
- }
517
- #panels-container.layout-3 {
518
- grid-template-columns: 1fr 1fr 1fr;
519
- grid-template-rows: 1fr;
520
- }
521
- #panels-container.layout-6 {
522
- grid-template-columns: 1fr 1fr 1fr;
523
- grid-template-rows: 1fr 1fr;
524
- }
525
-
526
- /* Individual panel */
527
- .sim-panel {
528
- position: relative;
529
- background: #f5f0e8;
530
- overflow: hidden;
531
- display: flex;
532
- flex-direction: column;
533
- }
534
-
535
- .panel-header {
536
- position: absolute;
537
- top: 8px;
538
- left: 8px;
539
- right: 8px;
540
- display: flex;
541
- align-items: center;
542
- gap: 6px;
543
- z-index: 5;
544
- pointer-events: none;
545
- }
546
- .panel-policy-dot {
547
- width: 10px;
548
- height: 10px;
549
- border-radius: 50%;
550
- flex-shrink: 0;
551
- }
552
- .panel-policy-name {
553
- font-size: 12px;
554
- font-weight: 700;
555
- letter-spacing: 0.5px;
556
- }
557
- /* Dark background box wrapping dot + policy name */
558
- .panel-label-box {
559
- display: flex;
560
- align-items: center;
561
- gap: 6px;
562
- background: rgba(0, 0, 0, 0.72);
563
- backdrop-filter: blur(4px);
564
- border-radius: 6px;
565
- padding: 4px 8px;
566
- pointer-events: none;
567
- }
568
- /* Per-panel zoom controls */
569
- .panel-zoom-controls {
570
- display: flex;
571
- gap: 3px;
572
- pointer-events: auto;
573
- margin-left: auto;
574
- }
575
- .panel-zoom-btn {
576
- background: rgba(0,0,0,0.45);
577
- backdrop-filter: blur(4px);
578
- border: 1px solid rgba(255,255,255,0.15);
579
- color: #e0dbd2;
580
- border-radius: 4px;
581
- width: 22px;
582
- height: 22px;
583
- font-size: 14px;
584
- line-height: 1;
585
- cursor: pointer;
586
- display: flex;
587
- align-items: center;
588
- justify-content: center;
589
- transition: background 0.15s;
590
- }
591
- .panel-zoom-btn:hover { background: rgba(0,0,0,0.65); }
592
-
593
- /* Per-panel metrics overlay (bottom-left of canvas) */
594
- .panel-metrics-overlay {
595
- position: absolute;
596
- bottom: 8px;
597
- left: 8px;
598
- z-index: 5;
599
- background: rgba(0, 0, 0, 0.72);
600
- backdrop-filter: blur(4px);
601
- border-radius: 6px;
602
- padding: 5px 9px;
603
- pointer-events: none;
604
- display: flex;
605
- flex-direction: column;
606
- gap: 2px;
607
- }
608
- .pmo-row {
609
- display: flex;
610
- justify-content: space-between;
611
- gap: 14px;
612
- font-size: 10px;
613
- }
614
- .pmo-label { color: #9ea8b8; }
615
- .pmo-value { color: #e2ddd6; font-variant-numeric: tabular-nums; }
616
-
617
- .panel-map-legend {
618
- position: absolute;
619
- right: 8px;
620
- bottom: 8px;
621
- z-index: 5;
622
- background: rgba(0, 0, 0, 0.72);
623
- backdrop-filter: blur(4px);
624
- border-radius: 6px;
625
- padding: 6px 8px;
626
- pointer-events: none;
627
- display: flex;
628
- flex-direction: column;
629
- gap: 3px;
630
- min-width: 108px;
631
- }
632
- .panel-map-legend.hidden { display: none; }
633
- .panel-map-legend-title {
634
- font-size: 9px;
635
- color: #b8c1cf;
636
- text-transform: uppercase;
637
- letter-spacing: 0.08em;
638
- font-weight: 700;
639
- margin-bottom: 1px;
640
- }
641
- .panel-map-legend-row {
642
- display: flex;
643
- align-items: center;
644
- gap: 6px;
645
- font-size: 10px;
646
- color: #e2ddd6;
647
- }
648
- .panel-map-swatch {
649
- display: inline-block;
650
- width: 18px;
651
- height: 8px;
652
- border-radius: 999px;
653
- flex-shrink: 0;
654
- }
655
- .panel-map-swatch.districts {
656
- background: rgba(95, 172, 248, 0.55);
657
- border: 1px solid rgba(95, 172, 248, 0.8);
658
- }
659
- .panel-map-swatch.entry { background: #22c55e; }
660
- .panel-map-swatch.exit { background: #ef4444; }
661
- .panel-map-swatch.gateway { background: #f39c12; }
662
-
663
- .panel-canvas-wrapper {
664
- flex: 1;
665
- position: relative;
666
- }
667
- .panel-canvas-wrapper canvas {
668
- display: block;
669
- width: 100% !important;
670
- height: 100% !important;
671
- }
672
-
673
- /* Coming-soon overlay for LLM+DQN */
674
- .coming-soon-overlay {
675
- position: absolute;
676
- inset: 0;
677
- background: rgba(17,19,24,0.88);
678
- display: flex;
679
- flex-direction: column;
680
- align-items: center;
681
- justify-content: center;
682
- gap: 8px;
683
- z-index: 10;
684
- }
685
- .coming-soon-overlay .cs-icon { font-size: 36px; }
686
- .coming-soon-overlay .cs-title {
687
- font-size: 15px;
688
- font-weight: 700;
689
- color: #fff;
690
- }
691
- .coming-soon-overlay .cs-sub {
692
- font-size: 12px;
693
- color: #777e90;
694
- }
695
-
696
- /* Panel loading spinner */
697
- .panel-spinner {
698
- position: absolute;
699
- inset: 0;
700
- display: flex;
701
- align-items: center;
702
- justify-content: center;
703
- background: #f5f0e8;
704
- z-index: 8;
705
- }
706
- .panel-spinner.hidden { display: none; }
707
- .spinner-ring {
708
- width: 36px;
709
- height: 36px;
710
- border: 3px solid #d4c9b8;
711
- border-top-color: #5b6cf9;
712
- border-radius: 50%;
713
- animation: spin 0.8s linear infinite;
714
- }
715
- @keyframes spin { to { transform: rotate(360deg); } }
716
-
717
- /* Welcome / idle state */
718
- #welcome-overlay {
719
- position: absolute;
720
- inset: 0;
721
- display: flex;
722
- align-items: center;
723
- justify-content: center;
724
- background: #f5f0e8;
725
- z-index: 20;
726
- flex-direction: column;
727
- gap: 10px;
728
- text-align: center;
729
- }
730
- #welcome-overlay h2 { font-size: 20px; color: #2e2820; margin: 0; }
731
- #welcome-overlay p { font-size: 13px; color: #7a6e60; margin: 0; max-width: 320px; }
732
- #welcome-overlay.hidden { display: none; }
733
-
734
- /* Policy color variables */
735
- :root {
736
- --color-no-intervention: #94a3b8;
737
- --color-fixed: #f59e0b;
738
- --color-random: #10b981;
739
- --color-learned: #5b6cf9;
740
- --color-llm-dqn: #ec4899;
741
- --color-dqn-heuristic: #06b6d4;
742
- }
743
-
744
- /* Responsive: if viewport too narrow, collapse sidebar */
745
- @media (max-width: 600px) {
746
- #sidebar { width: 200px; min-width: 200px; }
747
- }
 
1
+ /* =========================================================
2
+ Traffic Visualizer – Multi-Panel Dashboard Styles
3
+ ========================================================= */
4
+
5
+ *, *::before, *::after { box-sizing: border-box; }
6
+
7
+ body {
8
+ margin: 0;
9
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
10
+ background: #1a1d23;
11
+ color: #d4d8e0;
12
+ height: 100vh;
13
+ overflow: hidden;
14
+ display: flex;
15
+ }
16
+
17
+ /* ── Sidebar ──────────────────────────────────────────── */
18
+ #sidebar {
19
+ width: 270px;
20
+ min-width: 180px;
21
+ background: #22252e;
22
+ border-right: 1px solid #333744;
23
+ display: flex;
24
+ flex-direction: column;
25
+ overflow-y: auto;
26
+ padding: 16px;
27
+ gap: 16px;
28
+ z-index: 10;
29
+ scrollbar-width: thin;
30
+ scrollbar-color: #3a4055 transparent;
31
+ }
32
+ #sidebar::-webkit-scrollbar { width: 5px; }
33
+ #sidebar::-webkit-scrollbar-track { background: transparent; }
34
+ #sidebar::-webkit-scrollbar-thumb { background: #3a4055; border-radius: 3px; }
35
+ #sidebar::-webkit-scrollbar-thumb:hover { background: #5b6cf9; }
36
+
37
+ #sidebar h1 {
38
+ font-size: 18px;
39
+ font-weight: 700;
40
+ color: #fff;
41
+ margin: 0 0 4px 0;
42
+ letter-spacing: 0.5px;
43
+ }
44
+
45
+ #sidebar .subtitle {
46
+ font-size: 11px;
47
+ color: #777e90;
48
+ margin: 0;
49
+ }
50
+
51
+ .sidebar-section {
52
+ background: #1a1d23;
53
+ border-radius: 8px;
54
+ padding: 12px;
55
+ border: 1px solid #2e3240;
56
+ }
57
+
58
+ .sidebar-section h3 {
59
+ font-size: 11px;
60
+ font-weight: 600;
61
+ text-transform: uppercase;
62
+ letter-spacing: 1px;
63
+ color: #777e90;
64
+ margin: 0 0 10px 0;
65
+ }
66
+
67
+ /* File upload zone */
68
+ #roadnet-drop-zone {
69
+ border: 2px dashed #3a4055;
70
+ border-radius: 6px;
71
+ padding: 20px 12px;
72
+ text-align: center;
73
+ cursor: pointer;
74
+ transition: border-color 0.2s, background 0.2s;
75
+ font-size: 13px;
76
+ color: #777e90;
77
+ }
78
+ #roadnet-drop-zone:hover,
79
+ #roadnet-drop-zone.drag-over {
80
+ border-color: #5b6cf9;
81
+ background: rgba(91, 108, 249, 0.06);
82
+ color: #aab0c2;
83
+ }
84
+ #roadnet-drop-zone .drop-icon {
85
+ font-size: 24px;
86
+ margin-bottom: 6px;
87
+ display: block;
88
+ }
89
+ #roadnet-drop-zone .drop-filename {
90
+ font-size: 12px;
91
+ color: #5b6cf9;
92
+ font-weight: 600;
93
+ margin-top: 4px;
94
+ }
95
+ #roadnet-file-input { display: none; }
96
+
97
+ /* City/Scenario pickers */
98
+ .picker-row {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: 6px;
102
+ }
103
+ .picker-row label {
104
+ font-size: 11px;
105
+ color: #777e90;
106
+ font-weight: 600;
107
+ text-transform: uppercase;
108
+ letter-spacing: 0.5px;
109
+ }
110
+ .picker-row select {
111
+ width: 100%;
112
+ background: #22252e;
113
+ border: 1px solid #3a4055;
114
+ color: #d4d8e0;
115
+ border-radius: 5px;
116
+ padding: 6px 8px;
117
+ font-size: 13px;
118
+ outline: none;
119
+ cursor: pointer;
120
+ }
121
+ .picker-row select:focus {
122
+ border-color: #5b6cf9;
123
+ }
124
+ .picker-row select:disabled {
125
+ opacity: 0.4;
126
+ cursor: not-allowed;
127
+ }
128
+
129
+ /* Policy checkboxes */
130
+ .policy-list {
131
+ display: flex;
132
+ flex-direction: column;
133
+ gap: 7px;
134
+ }
135
+ .policy-item {
136
+ display: flex;
137
+ align-items: center;
138
+ gap: 8px;
139
+ cursor: pointer;
140
+ user-select: none;
141
+ }
142
+ .policy-item input[type="checkbox"] {
143
+ accent-color: var(--policy-color);
144
+ width: 15px;
145
+ height: 15px;
146
+ cursor: pointer;
147
+ }
148
+ .policy-item.disabled {
149
+ opacity: 0.4;
150
+ cursor: not-allowed;
151
+ }
152
+ .policy-item.disabled input { cursor: not-allowed; }
153
+
154
+ .policy-badge {
155
+ display: inline-block;
156
+ width: 10px;
157
+ height: 10px;
158
+ border-radius: 50%;
159
+ flex-shrink: 0;
160
+ }
161
+ .policy-label {
162
+ font-size: 13px;
163
+ flex: 1;
164
+ }
165
+ .policy-tag {
166
+ font-size: 10px;
167
+ background: rgba(255,255,255,0.07);
168
+ color: #888;
169
+ border-radius: 3px;
170
+ padding: 1px 5px;
171
+ }
172
+
173
+ .recent-runs-list {
174
+ display: flex;
175
+ flex-direction: column;
176
+ gap: 8px;
177
+ }
178
+ .recent-run-empty {
179
+ font-size: 12px;
180
+ color: #777e90;
181
+ }
182
+ .recent-run-card {
183
+ border: 1px solid #323747;
184
+ border-radius: 7px;
185
+ background: #22252e;
186
+ padding: 9px 10px;
187
+ display: flex;
188
+ flex-direction: column;
189
+ gap: 6px;
190
+ }
191
+ .recent-run-top {
192
+ display: flex;
193
+ justify-content: space-between;
194
+ gap: 8px;
195
+ align-items: baseline;
196
+ }
197
+ .recent-run-title {
198
+ font-size: 12px;
199
+ font-weight: 700;
200
+ color: #e7ecf4;
201
+ }
202
+ .recent-run-time {
203
+ font-size: 10px;
204
+ color: #7f8a9d;
205
+ white-space: nowrap;
206
+ }
207
+ .recent-run-meta {
208
+ font-size: 11px;
209
+ color: #aab4c4;
210
+ }
211
+ .recent-run-policies {
212
+ display: flex;
213
+ flex-wrap: wrap;
214
+ gap: 4px;
215
+ }
216
+ .recent-run-chip {
217
+ font-size: 10px;
218
+ color: #dbe2ee;
219
+ background: rgba(255,255,255,0.08);
220
+ border: 1px solid rgba(255,255,255,0.08);
221
+ border-radius: 999px;
222
+ padding: 2px 6px;
223
+ }
224
+
225
+ .debug-log {
226
+ max-height: 180px;
227
+ overflow: auto;
228
+ padding: 10px;
229
+ border-radius: 8px;
230
+ background: #161a22;
231
+ border: 1px solid #2c3442;
232
+ color: #dbe4ff;
233
+ font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
234
+ white-space: pre-wrap;
235
+ }
236
+ .recent-run-actions {
237
+ display: flex;
238
+ gap: 6px;
239
+ }
240
+ .recent-run-btn {
241
+ flex: 1;
242
+ background: #2b3040;
243
+ border: 1px solid #40475d;
244
+ color: #dce3ef;
245
+ border-radius: 5px;
246
+ padding: 5px 7px;
247
+ font-size: 11px;
248
+ cursor: pointer;
249
+ }
250
+ .recent-run-btn:hover {
251
+ border-color: #5b6cf9;
252
+ }
253
+
254
+ /* View mode selector */
255
+ .view-mode-row {
256
+ display: flex;
257
+ gap: 6px;
258
+ }
259
+ .view-mode-btn {
260
+ flex: 1;
261
+ background: #22252e;
262
+ border: 1px solid #3a4055;
263
+ color: #777e90;
264
+ border-radius: 5px;
265
+ padding: 6px 0;
266
+ font-size: 12px;
267
+ font-weight: 600;
268
+ cursor: pointer;
269
+ transition: all 0.15s;
270
+ text-align: center;
271
+ }
272
+ .view-mode-btn:hover { border-color: #5b6cf9; color: #aab0c2; }
273
+ .view-mode-btn.active {
274
+ background: #5b6cf9;
275
+ border-color: #5b6cf9;
276
+ color: #fff;
277
+ }
278
+
279
+ /* Run button */
280
+ #run-btn {
281
+ width: 100%;
282
+ padding: 10px;
283
+ background: linear-gradient(135deg, #5b6cf9, #7c3aed);
284
+ color: #fff;
285
+ border: none;
286
+ border-radius: 6px;
287
+ font-size: 14px;
288
+ font-weight: 600;
289
+ cursor: pointer;
290
+ transition: opacity 0.2s, transform 0.1s;
291
+ letter-spacing: 0.3px;
292
+ }
293
+ #run-btn:hover { opacity: 0.9; }
294
+ #run-btn:active { transform: scale(0.98); }
295
+ #run-btn:disabled { opacity: 0.4; cursor: not-allowed; }
296
+
297
+ /* Force-rerun checkbox */
298
+ .force-rerun-row {
299
+ display: flex;
300
+ align-items: center;
301
+ gap: 7px;
302
+ font-size: 11px;
303
+ color: #777e90;
304
+ cursor: pointer;
305
+ user-select: none;
306
+ margin-top: 2px;
307
+ }
308
+ .force-rerun-row input[type="checkbox"] {
309
+ accent-color: #5b6cf9;
310
+ width: 13px;
311
+ height: 13px;
312
+ cursor: pointer;
313
+ }
314
+
315
+ /* Progress */
316
+ #progress-section { display: none; }
317
+
318
+ .progress-policy-row {
319
+ padding: 6px 0;
320
+ border-bottom: 1px solid #2e3240;
321
+ }
322
+ .progress-policy-row:last-child { border-bottom: none; }
323
+
324
+ .progress-policy-top {
325
+ display: flex;
326
+ align-items: center;
327
+ gap: 8px;
328
+ margin-bottom: 5px;
329
+ }
330
+ .progress-policy-dot {
331
+ width: 8px;
332
+ height: 8px;
333
+ border-radius: 50%;
334
+ flex-shrink: 0;
335
+ }
336
+ .progress-policy-name { flex: 1; font-size: 12px; color: #d4d8e0; }
337
+ .progress-policy-pct {
338
+ font-size: 11px;
339
+ font-variant-numeric: tabular-nums;
340
+ color: #777e90;
341
+ min-width: 36px;
342
+ text-align: right;
343
+ }
344
+ .progress-policy-pct.done { color: #10b981; }
345
+ .progress-policy-pct.error { color: #ef4444; }
346
+
347
+ .progress-policy-bar-track {
348
+ height: 4px;
349
+ background: #2e3240;
350
+ border-radius: 2px;
351
+ overflow: hidden;
352
+ }
353
+ .progress-policy-bar-fill {
354
+ height: 100%;
355
+ border-radius: 2px;
356
+ width: 0%;
357
+ transition: width 0.15s ease;
358
+ }
359
+
360
+ /* Sidebar resize handle */
361
+ #sidebar-resize-handle {
362
+ width: 4px;
363
+ min-width: 4px;
364
+ background: #2e3240;
365
+ cursor: col-resize;
366
+ transition: background 0.15s;
367
+ z-index: 10;
368
+ flex-shrink: 0;
369
+ }
370
+ #sidebar-resize-handle:hover,
371
+ #sidebar-resize-handle.dragging {
372
+ background: #5b6cf9;
373
+ }
374
+
375
+ /* ── Main area ────────────────────────────────────────── */
376
+ #main {
377
+ flex: 1;
378
+ display: flex;
379
+ flex-direction: column;
380
+ overflow: hidden;
381
+ }
382
+
383
+ /* Playback toolbar */
384
+ #playback-bar {
385
+ background: #22252e;
386
+ border-bottom: 1px solid #333744;
387
+ padding: 8px 16px;
388
+ display: flex;
389
+ align-items: center;
390
+ gap: 12px;
391
+ flex-shrink: 0;
392
+ }
393
+ #playback-bar button {
394
+ background: #2e3240;
395
+ border: 1px solid #3a4055;
396
+ color: #d4d8e0;
397
+ border-radius: 5px;
398
+ padding: 5px 14px;
399
+ font-size: 13px;
400
+ cursor: pointer;
401
+ transition: background 0.15s;
402
+ }
403
+ #playback-bar button:hover { background: #3a4055; }
404
+ #playback-bar button:disabled { opacity: 0.4; cursor: not-allowed; }
405
+
406
+ #summary-bar {
407
+ background: #1c2028;
408
+ border-bottom: 1px solid #2c3341;
409
+ padding: 10px 12px;
410
+ display: flex;
411
+ gap: 10px;
412
+ overflow-x: auto;
413
+ flex-shrink: 0;
414
+ }
415
+
416
+ .summary-empty {
417
+ color: #8d97aa;
418
+ font-size: 13px;
419
+ }
420
+
421
+ .summary-card {
422
+ min-width: 245px;
423
+ background: #222733;
424
+ border: 1px solid #313949;
425
+ border-top: 3px solid var(--summary-color, #5b6cf9);
426
+ border-radius: 10px;
427
+ padding: 10px 12px;
428
+ display: flex;
429
+ flex-direction: column;
430
+ gap: 8px;
431
+ }
432
+
433
+ .summary-card-header {
434
+ display: flex;
435
+ justify-content: space-between;
436
+ align-items: baseline;
437
+ gap: 8px;
438
+ }
439
+
440
+ .summary-card-title {
441
+ font-size: 13px;
442
+ font-weight: 700;
443
+ color: #edf2ff;
444
+ }
445
+
446
+ .summary-card-subtitle {
447
+ font-size: 10px;
448
+ color: #9aa4b8;
449
+ text-transform: uppercase;
450
+ letter-spacing: 0.08em;
451
+ }
452
+
453
+ .summary-grid {
454
+ display: grid;
455
+ grid-template-columns: 1fr 1fr;
456
+ gap: 6px 10px;
457
+ }
458
+
459
+ .summary-stat-label {
460
+ font-size: 10px;
461
+ color: #8893a8;
462
+ text-transform: uppercase;
463
+ letter-spacing: 0.08em;
464
+ }
465
+
466
+ .summary-stat-value {
467
+ font-size: 13px;
468
+ color: #f5f7fb;
469
+ font-weight: 600;
470
+ }
471
+
472
+ .summary-delta {
473
+ font-size: 11px;
474
+ font-weight: 600;
475
+ }
476
+
477
+ .summary-delta.good { color: #34d399; }
478
+ .summary-delta.bad { color: #f87171; }
479
+ .summary-delta.neutral { color: #94a3b8; }
480
+
481
+ #scrubber {
482
+ flex: 1;
483
+ accent-color: #5b6cf9;
484
+ cursor: pointer;
485
+ }
486
+ #step-display {
487
+ font-size: 12px;
488
+ color: #777e90;
489
+ font-variant-numeric: tabular-nums;
490
+ min-width: 90px;
491
+ text-align: right;
492
+ }
493
+ #speed-label {
494
+ font-size: 12px;
495
+ color: #777e90;
496
+ min-width: 48px;
497
+ }
498
+
499
+ /* Panel grid */
500
+ #panels-container {
501
+ flex: 1;
502
+ overflow: hidden;
503
+ display: grid;
504
+ gap: 2px;
505
+ background: #111318;
506
+ }
507
+
508
+ /* Grid layouts */
509
+ #panels-container.layout-1 {
510
+ grid-template-columns: 1fr;
511
+ grid-template-rows: 1fr;
512
+ }
513
+ #panels-container.layout-2 {
514
+ grid-template-columns: 1fr 1fr;
515
+ grid-template-rows: 1fr;
516
+ }
517
+ #panels-container.layout-3 {
518
+ grid-template-columns: 1fr 1fr 1fr;
519
+ grid-template-rows: 1fr;
520
+ }
521
+ #panels-container.layout-6 {
522
+ grid-template-columns: 1fr 1fr 1fr;
523
+ grid-template-rows: 1fr 1fr;
524
+ }
525
+
526
+ /* Individual panel */
527
+ .sim-panel {
528
+ position: relative;
529
+ background: #f5f0e8;
530
+ overflow: hidden;
531
+ display: flex;
532
+ flex-direction: column;
533
+ }
534
+
535
+ .panel-header {
536
+ position: absolute;
537
+ top: 8px;
538
+ left: 8px;
539
+ right: 8px;
540
+ display: flex;
541
+ align-items: center;
542
+ gap: 6px;
543
+ z-index: 5;
544
+ pointer-events: none;
545
+ }
546
+ .panel-policy-dot {
547
+ width: 10px;
548
+ height: 10px;
549
+ border-radius: 50%;
550
+ flex-shrink: 0;
551
+ }
552
+ .panel-policy-name {
553
+ font-size: 12px;
554
+ font-weight: 700;
555
+ letter-spacing: 0.5px;
556
+ }
557
+ /* Dark background box wrapping dot + policy name */
558
+ .panel-label-box {
559
+ display: flex;
560
+ align-items: center;
561
+ gap: 6px;
562
+ background: rgba(0, 0, 0, 0.72);
563
+ backdrop-filter: blur(4px);
564
+ border-radius: 6px;
565
+ padding: 4px 8px;
566
+ pointer-events: none;
567
+ }
568
+ /* Per-panel zoom controls */
569
+ .panel-zoom-controls {
570
+ display: flex;
571
+ gap: 3px;
572
+ pointer-events: auto;
573
+ margin-left: auto;
574
+ }
575
+ .panel-zoom-btn {
576
+ background: rgba(0,0,0,0.45);
577
+ backdrop-filter: blur(4px);
578
+ border: 1px solid rgba(255,255,255,0.15);
579
+ color: #e0dbd2;
580
+ border-radius: 4px;
581
+ width: 22px;
582
+ height: 22px;
583
+ font-size: 14px;
584
+ line-height: 1;
585
+ cursor: pointer;
586
+ display: flex;
587
+ align-items: center;
588
+ justify-content: center;
589
+ transition: background 0.15s;
590
+ }
591
+ .panel-zoom-btn:hover { background: rgba(0,0,0,0.65); }
592
+
593
+ /* Per-panel metrics overlay (bottom-left of canvas) */
594
+ .panel-metrics-overlay {
595
+ position: absolute;
596
+ bottom: 8px;
597
+ left: 8px;
598
+ z-index: 5;
599
+ background: rgba(0, 0, 0, 0.72);
600
+ backdrop-filter: blur(4px);
601
+ border-radius: 6px;
602
+ padding: 5px 9px;
603
+ pointer-events: none;
604
+ display: flex;
605
+ flex-direction: column;
606
+ gap: 2px;
607
+ }
608
+ .pmo-row {
609
+ display: flex;
610
+ justify-content: space-between;
611
+ gap: 14px;
612
+ font-size: 10px;
613
+ }
614
+ .pmo-label { color: #9ea8b8; }
615
+ .pmo-value { color: #e2ddd6; font-variant-numeric: tabular-nums; }
616
+
617
+ .panel-map-legend {
618
+ position: absolute;
619
+ right: 8px;
620
+ bottom: 8px;
621
+ z-index: 5;
622
+ background: rgba(0, 0, 0, 0.72);
623
+ backdrop-filter: blur(4px);
624
+ border-radius: 6px;
625
+ padding: 6px 8px;
626
+ pointer-events: none;
627
+ display: flex;
628
+ flex-direction: column;
629
+ gap: 3px;
630
+ min-width: 108px;
631
+ }
632
+ .panel-map-legend.hidden { display: none; }
633
+ .panel-map-legend-title {
634
+ font-size: 9px;
635
+ color: #b8c1cf;
636
+ text-transform: uppercase;
637
+ letter-spacing: 0.08em;
638
+ font-weight: 700;
639
+ margin-bottom: 1px;
640
+ }
641
+ .panel-map-legend-row {
642
+ display: flex;
643
+ align-items: center;
644
+ gap: 6px;
645
+ font-size: 10px;
646
+ color: #e2ddd6;
647
+ }
648
+ .panel-map-swatch {
649
+ display: inline-block;
650
+ width: 18px;
651
+ height: 8px;
652
+ border-radius: 999px;
653
+ flex-shrink: 0;
654
+ }
655
+ .panel-map-swatch.districts {
656
+ background: rgba(95, 172, 248, 0.55);
657
+ border: 1px solid rgba(95, 172, 248, 0.8);
658
+ }
659
+ .panel-map-swatch.entry { background: #22c55e; }
660
+ .panel-map-swatch.exit { background: #ef4444; }
661
+ .panel-map-swatch.gateway { background: #f39c12; }
662
+
663
+ .panel-canvas-wrapper {
664
+ flex: 1;
665
+ position: relative;
666
+ }
667
+ .panel-canvas-wrapper canvas {
668
+ display: block;
669
+ width: 100% !important;
670
+ height: 100% !important;
671
+ }
672
+
673
+ /* Coming-soon overlay for LLM+DQN */
674
+ .coming-soon-overlay {
675
+ position: absolute;
676
+ inset: 0;
677
+ background: rgba(17,19,24,0.88);
678
+ display: flex;
679
+ flex-direction: column;
680
+ align-items: center;
681
+ justify-content: center;
682
+ gap: 8px;
683
+ z-index: 10;
684
+ }
685
+ .coming-soon-overlay .cs-icon { font-size: 36px; }
686
+ .coming-soon-overlay .cs-title {
687
+ font-size: 15px;
688
+ font-weight: 700;
689
+ color: #fff;
690
+ }
691
+ .coming-soon-overlay .cs-sub {
692
+ font-size: 12px;
693
+ color: #777e90;
694
+ }
695
+
696
+ /* Panel loading spinner */
697
+ .panel-spinner {
698
+ position: absolute;
699
+ inset: 0;
700
+ display: flex;
701
+ align-items: center;
702
+ justify-content: center;
703
+ background: #f5f0e8;
704
+ z-index: 8;
705
+ }
706
+ .panel-spinner.hidden { display: none; }
707
+ .spinner-ring {
708
+ width: 36px;
709
+ height: 36px;
710
+ border: 3px solid #d4c9b8;
711
+ border-top-color: #5b6cf9;
712
+ border-radius: 50%;
713
+ animation: spin 0.8s linear infinite;
714
+ }
715
+ @keyframes spin { to { transform: rotate(360deg); } }
716
+
717
+ /* Welcome / idle state */
718
+ #welcome-overlay {
719
+ position: absolute;
720
+ inset: 0;
721
+ display: flex;
722
+ align-items: center;
723
+ justify-content: center;
724
+ background: #f5f0e8;
725
+ z-index: 20;
726
+ flex-direction: column;
727
+ gap: 10px;
728
+ text-align: center;
729
+ }
730
+ #welcome-overlay h2 { font-size: 20px; color: #2e2820; margin: 0; }
731
+ #welcome-overlay p { font-size: 13px; color: #7a6e60; margin: 0; max-width: 320px; }
732
+ #welcome-overlay.hidden { display: none; }
733
+
734
+ /* Policy color variables */
735
+ :root {
736
+ --color-no-intervention: #94a3b8;
737
+ --color-fixed: #f59e0b;
738
+ --color-random: #10b981;
739
+ --color-learned: #5b6cf9;
740
+ --color-llm-dqn: #ec4899;
741
+ --color-dqn-heuristic: #06b6d4;
742
+ }
743
+
744
+ /* Responsive: if viewport too narrow, collapse sidebar */
745
+ @media (max-width: 600px) {
746
+ #sidebar { width: 200px; min-width: 200px; }
747
+ }
third_party/CityFlow/setup.py CHANGED
@@ -1,70 +1,70 @@
1
- import os
2
- import re
3
- import sys
4
- import platform
5
- import subprocess
6
-
7
- from setuptools import setup, Extension
8
- from setuptools.command.build_ext import build_ext
9
- from distutils.version import LooseVersion
10
-
11
-
12
- class CMakeExtension(Extension):
13
- def __init__(self, name, sourcedir=''):
14
- Extension.__init__(self, name, sources=[])
15
- self.sourcedir = os.path.abspath(sourcedir)
16
-
17
-
18
- class CMakeBuild(build_ext):
19
- def run(self):
20
- try:
21
- out = subprocess.check_output(['cmake', '--version'])
22
- except OSError:
23
- raise RuntimeError("CMake must be installed to build the following extensions: " +
24
- ", ".join(e.name for e in self.extensions))
25
-
26
- if platform.system() == "Windows":
27
- cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1))
28
- if cmake_version < '3.1.0':
29
- raise RuntimeError("CMake >= 3.1.0 is required on Windows")
30
-
31
- for ext in self.extensions:
32
- self.build_extension(ext)
33
-
34
- def build_extension(self, ext):
35
- extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
36
- cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
37
- '-DPYTHON_EXECUTABLE=' + sys.executable,
38
- '-DVERSION="' + self.distribution.get_version() + '"']
39
-
40
- cfg = 'Debug' if self.debug else 'Release'
41
- build_args = ['--config', cfg]
42
-
43
- if platform.system() == "Windows":
44
- cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
45
- if sys.maxsize > 2**32:
46
- cmake_args += ['-A', 'x64']
47
- build_args += ['--', '/m']
48
- else:
49
- cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
50
- build_args += ['--', '-j2']
51
-
52
- env = os.environ.copy()
53
-
54
- if not os.path.exists(self.build_temp):
55
- os.makedirs(self.build_temp)
56
- subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env)
57
- subprocess.check_call(['cmake', '--build', '.', '--target', 'cityflow'] + build_args, cwd=self.build_temp)
58
-
59
-
60
- setup(
61
- name='CityFlow',
62
- version='0.1',
63
- author='Huichu Zhang',
64
- author_email='zhc@apex.sjtu.edu.cn',
65
- description='CityFlow: A Multi-Agent Reinforcement Learning Environment for Large Scale City Traffic Scenario',
66
- long_description='',
67
- ext_modules=[CMakeExtension('cityflow')],
68
- cmdclass=dict(build_ext=CMakeBuild),
69
- zip_safe=False
70
- )
 
1
+ import os
2
+ import re
3
+ import sys
4
+ import platform
5
+ import subprocess
6
+
7
+ from setuptools import setup, Extension
8
+ from setuptools.command.build_ext import build_ext
9
+ from distutils.version import LooseVersion
10
+
11
+
12
+ class CMakeExtension(Extension):
13
+ def __init__(self, name, sourcedir=''):
14
+ Extension.__init__(self, name, sources=[])
15
+ self.sourcedir = os.path.abspath(sourcedir)
16
+
17
+
18
+ class CMakeBuild(build_ext):
19
+ def run(self):
20
+ try:
21
+ out = subprocess.check_output(['cmake', '--version'])
22
+ except OSError:
23
+ raise RuntimeError("CMake must be installed to build the following extensions: " +
24
+ ", ".join(e.name for e in self.extensions))
25
+
26
+ if platform.system() == "Windows":
27
+ cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1))
28
+ if cmake_version < '3.1.0':
29
+ raise RuntimeError("CMake >= 3.1.0 is required on Windows")
30
+
31
+ for ext in self.extensions:
32
+ self.build_extension(ext)
33
+
34
+ def build_extension(self, ext):
35
+ extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
36
+ cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
37
+ '-DPYTHON_EXECUTABLE=' + sys.executable,
38
+ '-DVERSION="' + self.distribution.get_version() + '"']
39
+
40
+ cfg = 'Debug' if self.debug else 'Release'
41
+ build_args = ['--config', cfg]
42
+
43
+ if platform.system() == "Windows":
44
+ cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
45
+ if sys.maxsize > 2**32:
46
+ cmake_args += ['-A', 'x64']
47
+ build_args += ['--', '/m']
48
+ else:
49
+ cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
50
+ build_args += ['--', '-j2']
51
+
52
+ env = os.environ.copy()
53
+
54
+ if not os.path.exists(self.build_temp):
55
+ os.makedirs(self.build_temp)
56
+ subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env)
57
+ subprocess.check_call(['cmake', '--build', '.', '--target', 'cityflow'] + build_args, cwd=self.build_temp)
58
+
59
+
60
+ setup(
61
+ name='CityFlow',
62
+ version='0.1',
63
+ author='Huichu Zhang',
64
+ author_email='zhc@apex.sjtu.edu.cn',
65
+ description='CityFlow: A Multi-Agent Reinforcement Learning Environment for Large Scale City Traffic Scenario',
66
+ long_description='',
67
+ ext_modules=[CMakeExtension('cityflow')],
68
+ cmdclass=dict(build_ext=CMakeBuild),
69
+ zip_safe=False
70
+ )
third_party/CityFlow/src/CMakeLists.txt CHANGED
@@ -1,36 +1,36 @@
1
- set(PROJECT_HEADER_FILES
2
- utility/config.h
3
- utility/utility.h
4
- utility/barrier.h
5
- utility/optionparser.h
6
- engine/archive.h
7
- engine/engine.h
8
- flow/flow.h
9
- flow/route.h
10
- roadnet/roadnet.h
11
- roadnet/trafficlight.h
12
- vehicle/router.h
13
- vehicle/vehicle.h
14
- vehicle/lanechange.h
15
- )
16
-
17
- set(PROJECT_SOURCE_FILES
18
- utility/utility.cpp
19
- utility/barrier.cpp
20
- engine/archive.cpp
21
- engine/engine.cpp
22
- flow/flow.cpp
23
- roadnet/roadnet.cpp
24
- roadnet/trafficlight.cpp
25
- vehicle/router.cpp
26
- vehicle/vehicle.cpp
27
- vehicle/lanechange.cpp)
28
-
29
- set(PROJECT_LIB_NAME ${PROJECT_NAME}_lib CACHE INTERNAL "")
30
-
31
- find_package(Threads REQUIRED)
32
-
33
- add_library(${PROJECT_LIB_NAME} ${PROJECT_HEADER_FILES} ${PROJECT_SOURCE_FILES})
34
- set_target_properties(${PROJECT_LIB_NAME} PROPERTIES CXX_VISIBILITY_PRESET "hidden")
35
- target_link_libraries(${PROJECT_LIB_NAME} PRIVATE Threads::Threads)
36
  target_include_directories(${PROJECT_LIB_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR})
 
1
+ set(PROJECT_HEADER_FILES
2
+ utility/config.h
3
+ utility/utility.h
4
+ utility/barrier.h
5
+ utility/optionparser.h
6
+ engine/archive.h
7
+ engine/engine.h
8
+ flow/flow.h
9
+ flow/route.h
10
+ roadnet/roadnet.h
11
+ roadnet/trafficlight.h
12
+ vehicle/router.h
13
+ vehicle/vehicle.h
14
+ vehicle/lanechange.h
15
+ )
16
+
17
+ set(PROJECT_SOURCE_FILES
18
+ utility/utility.cpp
19
+ utility/barrier.cpp
20
+ engine/archive.cpp
21
+ engine/engine.cpp
22
+ flow/flow.cpp
23
+ roadnet/roadnet.cpp
24
+ roadnet/trafficlight.cpp
25
+ vehicle/router.cpp
26
+ vehicle/vehicle.cpp
27
+ vehicle/lanechange.cpp)
28
+
29
+ set(PROJECT_LIB_NAME ${PROJECT_NAME}_lib CACHE INTERNAL "")
30
+
31
+ find_package(Threads REQUIRED)
32
+
33
+ add_library(${PROJECT_LIB_NAME} ${PROJECT_HEADER_FILES} ${PROJECT_SOURCE_FILES})
34
+ set_target_properties(${PROJECT_LIB_NAME} PROPERTIES CXX_VISIBILITY_PRESET "hidden")
35
+ target_link_libraries(${PROJECT_LIB_NAME} PRIVATE Threads::Threads)
36
  target_include_directories(${PROJECT_LIB_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR})
third_party/CityFlow/src/cityflow.cpp CHANGED
@@ -1,48 +1,48 @@
1
- #include "engine/engine.h"
2
- #include "engine/archive.h"
3
-
4
- #include "pybind11/pybind11.h"
5
- #include "pybind11/stl.h"
6
-
7
- namespace py = pybind11;
8
- using namespace py::literals;
9
-
10
- PYBIND11_MODULE(cityflow, m) {
11
- py::class_<CityFlow::Engine>(m, "Engine")
12
- .def(py::init<const std::string&, int>(),
13
- "config_file"_a,
14
- "thread_num"_a=1
15
- )
16
- .def("next_step", &CityFlow::Engine::nextStep)
17
- .def("get_vehicle_count", &CityFlow::Engine::getVehicleCount)
18
- .def("get_vehicles", &CityFlow::Engine::getVehicles, "include_waiting"_a=false)
19
- .def("get_lane_vehicle_count", &CityFlow::Engine::getLaneVehicleCount)
20
- .def("get_lane_waiting_vehicle_count", &CityFlow::Engine::getLaneWaitingVehicleCount)
21
- .def("get_lane_vehicles", &CityFlow::Engine::getLaneVehicles)
22
- .def("get_vehicle_speed", &CityFlow::Engine::getVehicleSpeed)
23
- .def("get_vehicle_info", &CityFlow::Engine::getVehicleInfo, "vehicle_id"_a)
24
- .def("get_vehicle_distance", &CityFlow::Engine::getVehicleDistance)
25
- .def("get_leader", &CityFlow::Engine::getLeader, "vehicle_id"_a)
26
- .def("get_current_time", &CityFlow::Engine::getCurrentTime)
27
- .def("get_average_travel_time", &CityFlow::Engine::getAverageTravelTime)
28
- .def("set_tl_phase", &CityFlow::Engine::setTrafficLightPhase, "intersection_id"_a, "phase_id"_a)
29
- .def("set_vehicle_speed", &CityFlow::Engine::setVehicleSpeed, "vehicle_id"_a, "speed"_a)
30
- .def("set_replay_file", &CityFlow::Engine::setReplayLogFile, "replay_file"_a)
31
- .def("set_random_seed", &CityFlow::Engine::setRandomSeed, "seed"_a)
32
- .def("set_save_replay", &CityFlow::Engine::setSaveReplay, "open"_a)
33
- .def("push_vehicle", (void (CityFlow::Engine::*)(const std::map<std::string, double>&, const std::vector<std::string>&)) &CityFlow::Engine::pushVehicle)
34
- .def("reset", &CityFlow::Engine::reset, "seed"_a=false)
35
- .def("load", &CityFlow::Engine::load, "archive"_a)
36
- .def("snapshot", &CityFlow::Engine::snapshot)
37
- .def("load_from_file", &CityFlow::Engine::loadFromFile, "path"_a)
38
- .def("set_vehicle_route", &CityFlow::Engine::setRoute, "vehicle_id"_a, "route"_a);
39
-
40
- py::class_<CityFlow::Archive>(m, "Archive")
41
- .def(py::init<const CityFlow::Engine&>())
42
- .def("dump", &CityFlow::Archive::dump, "path"_a);
43
- #ifdef VERSION
44
- m.attr("__version__") = VERSION;
45
- #else
46
- m.attr("__version__") = "dev";
47
- #endif
48
  }
 
1
+ #include "engine/engine.h"
2
+ #include "engine/archive.h"
3
+
4
+ #include "pybind11/pybind11.h"
5
+ #include "pybind11/stl.h"
6
+
7
+ namespace py = pybind11;
8
+ using namespace py::literals;
9
+
10
+ PYBIND11_MODULE(cityflow, m) {
11
+ py::class_<CityFlow::Engine>(m, "Engine")
12
+ .def(py::init<const std::string&, int>(),
13
+ "config_file"_a,
14
+ "thread_num"_a=1
15
+ )
16
+ .def("next_step", &CityFlow::Engine::nextStep)
17
+ .def("get_vehicle_count", &CityFlow::Engine::getVehicleCount)
18
+ .def("get_vehicles", &CityFlow::Engine::getVehicles, "include_waiting"_a=false)
19
+ .def("get_lane_vehicle_count", &CityFlow::Engine::getLaneVehicleCount)
20
+ .def("get_lane_waiting_vehicle_count", &CityFlow::Engine::getLaneWaitingVehicleCount)
21
+ .def("get_lane_vehicles", &CityFlow::Engine::getLaneVehicles)
22
+ .def("get_vehicle_speed", &CityFlow::Engine::getVehicleSpeed)
23
+ .def("get_vehicle_info", &CityFlow::Engine::getVehicleInfo, "vehicle_id"_a)
24
+ .def("get_vehicle_distance", &CityFlow::Engine::getVehicleDistance)
25
+ .def("get_leader", &CityFlow::Engine::getLeader, "vehicle_id"_a)
26
+ .def("get_current_time", &CityFlow::Engine::getCurrentTime)
27
+ .def("get_average_travel_time", &CityFlow::Engine::getAverageTravelTime)
28
+ .def("set_tl_phase", &CityFlow::Engine::setTrafficLightPhase, "intersection_id"_a, "phase_id"_a)
29
+ .def("set_vehicle_speed", &CityFlow::Engine::setVehicleSpeed, "vehicle_id"_a, "speed"_a)
30
+ .def("set_replay_file", &CityFlow::Engine::setReplayLogFile, "replay_file"_a)
31
+ .def("set_random_seed", &CityFlow::Engine::setRandomSeed, "seed"_a)
32
+ .def("set_save_replay", &CityFlow::Engine::setSaveReplay, "open"_a)
33
+ .def("push_vehicle", (void (CityFlow::Engine::*)(const std::map<std::string, double>&, const std::vector<std::string>&)) &CityFlow::Engine::pushVehicle)
34
+ .def("reset", &CityFlow::Engine::reset, "seed"_a=false)
35
+ .def("load", &CityFlow::Engine::load, "archive"_a)
36
+ .def("snapshot", &CityFlow::Engine::snapshot)
37
+ .def("load_from_file", &CityFlow::Engine::loadFromFile, "path"_a)
38
+ .def("set_vehicle_route", &CityFlow::Engine::setRoute, "vehicle_id"_a, "route"_a);
39
+
40
+ py::class_<CityFlow::Archive>(m, "Archive")
41
+ .def(py::init<const CityFlow::Engine&>())
42
+ .def("dump", &CityFlow::Archive::dump, "path"_a);
43
+ #ifdef VERSION
44
+ m.attr("__version__") = VERSION;
45
+ #else
46
+ m.attr("__version__") = "dev";
47
+ #endif
48
  }
third_party/CityFlow/tests/CMakeLists.txt CHANGED
@@ -1,14 +1,14 @@
1
- file(GLOB TEST_SRCS cpp/*.cpp)
2
- set(FLAGS "-fsanitize=address -fno-omit-frame-pointer")
3
- include_directories(${GTEST_INCLUDE_DIRS})
4
- foreach(SRC_PATH ${TEST_SRCS})
5
- get_filename_component(SRC_NAME ${SRC_PATH} NAME)
6
- string(REPLACE ".cpp" "" EXE_NAME ${SRC_NAME})
7
- add_executable(${EXE_NAME} ${SRC_PATH})
8
- set_target_properties(${EXE_NAME} PROPERTIES APPEND_STRING PROPERTY LINK_FLAGS "${FLAGS}")
9
- set_target_properties(${EXE_NAME} PROPERTIES APPEND_STRING PROPERTY COMPILE_FLAGS "${FLAGS}")
10
- target_link_libraries(${EXE_NAME} PUBLIC ${PROJECT_LIB_NAME} ${GTEST_BOTH_LIBRARIES})
11
- add_test(NAME ${EXE_NAME}
12
- WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
13
- COMMAND ${EXE_NAME})
14
  endforeach()
 
1
+ file(GLOB TEST_SRCS cpp/*.cpp)
2
+ set(FLAGS "-fsanitize=address -fno-omit-frame-pointer")
3
+ include_directories(${GTEST_INCLUDE_DIRS})
4
+ foreach(SRC_PATH ${TEST_SRCS})
5
+ get_filename_component(SRC_NAME ${SRC_PATH} NAME)
6
+ string(REPLACE ".cpp" "" EXE_NAME ${SRC_NAME})
7
+ add_executable(${EXE_NAME} ${SRC_PATH})
8
+ set_target_properties(${EXE_NAME} PROPERTIES APPEND_STRING PROPERTY LINK_FLAGS "${FLAGS}")
9
+ set_target_properties(${EXE_NAME} PROPERTIES APPEND_STRING PROPERTY COMPILE_FLAGS "${FLAGS}")
10
+ target_link_libraries(${EXE_NAME} PUBLIC ${PROJECT_LIB_NAME} ${GTEST_BOTH_LIBRARIES})
11
+ add_test(NAME ${EXE_NAME}
12
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
13
+ COMMAND ${EXE_NAME})
14
  endforeach()
third_party/CityFlow/tools/debug/CMakeLists.txt CHANGED
@@ -1,9 +1,9 @@
1
- set(SOURCE_FILES simple_run.cpp)
2
-
3
- foreach(SRC_PATH ${SOURCE_FILES})
4
- get_filename_component(SRC_NAME ${SRC_PATH} NAME)
5
- string(REPLACE ".cpp" "" EXE_NAME ${SRC_NAME})
6
- add_executable(${EXE_NAME} ${SRC_PATH})
7
- set_target_properties(${EXE_NAME} PROPERTIES CXX_VISIBILITY_PRESET "hidden")
8
- target_link_libraries(${EXE_NAME} PRIVATE ${PROJECT_LIB_NAME})
9
  endforeach()
 
1
+ set(SOURCE_FILES simple_run.cpp)
2
+
3
+ foreach(SRC_PATH ${SOURCE_FILES})
4
+ get_filename_component(SRC_NAME ${SRC_PATH} NAME)
5
+ string(REPLACE ".cpp" "" EXE_NAME ${SRC_NAME})
6
+ add_executable(${EXE_NAME} ${SRC_PATH})
7
+ set_target_properties(${EXE_NAME} PROPERTIES CXX_VISIBILITY_PRESET "hidden")
8
+ target_link_libraries(${EXE_NAME} PRIVATE ${PROJECT_LIB_NAME})
9
  endforeach()
third_party/CityFlow/tools/debug/simple_run.cpp CHANGED
@@ -1,59 +1,59 @@
1
- #include "engine/engine.h"
2
- #include "utility/optionparser.h"
3
-
4
- #include <string>
5
- #include <iostream>
6
- #include <cstdlib>
7
- #include <ctime>
8
-
9
- using namespace CityFlow;
10
-
11
- int main(int argc, char const *argv[]) {
12
- optionparser::OptionParser parser;
13
-
14
- parser.add_option("--configFile", "-c")
15
- .help("config file")
16
- .mode(optionparser::StorageMode::STORE_VALUE)
17
- .required(true);
18
-
19
- parser.add_option("--totalStep", "-s")
20
- .help("simulation steps")
21
- .default_value(1000)
22
- .mode(optionparser::StorageMode::STORE_VALUE);
23
-
24
- parser.add_option("--threadNum", "-t")
25
- .help("number of threads")
26
- .default_value(1)
27
- .mode(optionparser::StorageMode::STORE_VALUE);
28
-
29
- parser.add_option("--verbose", "-v")
30
- .help("be verbose")
31
- .mode(optionparser::StorageMode::STORE_TRUE);
32
-
33
- parser.eat_arguments(argc, argv);
34
- std::string configFile = parser.get_value<std::string>("configFile");
35
- bool verbose = parser.get_value("verbose");
36
- size_t totalStep = parser.get_value<int>("totalStep");
37
- size_t threadNum = parser.get_value<int>("threadNum");
38
-
39
- std::string dataDir(std::getenv("DATADIR"));
40
-
41
- Engine engine(dataDir + configFile, (size_t) threadNum);
42
- time_t startTime, endTime;
43
- time(&startTime);
44
- for (int i = 0; i < totalStep; i++) {
45
- if (verbose) {
46
- std::cout << i << " " << engine.getVehicleCount() << std::endl;
47
- }
48
- engine.nextStep();
49
- //engine.getVehicleSpeed();
50
- //engine.getLaneVehicles();
51
- //engine.getLaneWaitingVehicleCount();
52
- //engine.getVehicleDistance();
53
- //engine.getCurrentTime();
54
- }
55
- time(&endTime);
56
- std::cout << "Total Step: " << totalStep << std::endl;
57
- std::cout << "Total Time: " << (endTime - startTime) << "s" << std::endl;
58
- return 0;
59
  }
 
1
+ #include "engine/engine.h"
2
+ #include "utility/optionparser.h"
3
+
4
+ #include <string>
5
+ #include <iostream>
6
+ #include <cstdlib>
7
+ #include <ctime>
8
+
9
+ using namespace CityFlow;
10
+
11
+ int main(int argc, char const *argv[]) {
12
+ optionparser::OptionParser parser;
13
+
14
+ parser.add_option("--configFile", "-c")
15
+ .help("config file")
16
+ .mode(optionparser::StorageMode::STORE_VALUE)
17
+ .required(true);
18
+
19
+ parser.add_option("--totalStep", "-s")
20
+ .help("simulation steps")
21
+ .default_value(1000)
22
+ .mode(optionparser::StorageMode::STORE_VALUE);
23
+
24
+ parser.add_option("--threadNum", "-t")
25
+ .help("number of threads")
26
+ .default_value(1)
27
+ .mode(optionparser::StorageMode::STORE_VALUE);
28
+
29
+ parser.add_option("--verbose", "-v")
30
+ .help("be verbose")
31
+ .mode(optionparser::StorageMode::STORE_TRUE);
32
+
33
+ parser.eat_arguments(argc, argv);
34
+ std::string configFile = parser.get_value<std::string>("configFile");
35
+ bool verbose = parser.get_value("verbose");
36
+ size_t totalStep = parser.get_value<int>("totalStep");
37
+ size_t threadNum = parser.get_value<int>("threadNum");
38
+
39
+ std::string dataDir(std::getenv("DATADIR"));
40
+
41
+ Engine engine(dataDir + configFile, (size_t) threadNum);
42
+ time_t startTime, endTime;
43
+ time(&startTime);
44
+ for (int i = 0; i < totalStep; i++) {
45
+ if (verbose) {
46
+ std::cout << i << " " << engine.getVehicleCount() << std::endl;
47
+ }
48
+ engine.nextStep();
49
+ //engine.getVehicleSpeed();
50
+ //engine.getLaneVehicles();
51
+ //engine.getLaneWaitingVehicleCount();
52
+ //engine.getVehicleDistance();
53
+ //engine.getCurrentTime();
54
+ }
55
+ time(&endTime);
56
+ std::cout << "Total Step: " << totalStep << std::endl;
57
+ std::cout << "Total Time: " << (endTime - startTime) << "s" << std::endl;
58
+ return 0;
59
  }
third_party/CityFlow/tools/generator/generate_grid_scenario.py CHANGED
@@ -1,127 +1,127 @@
1
- import argparse
2
- import json
3
- import os
4
- from generate_json_from_grid import gridToRoadnet
5
-
6
- def parse_args():
7
- parser = argparse.ArgumentParser()
8
- parser.add_argument("rowNum", type=int)
9
- parser.add_argument("colNum", type=int)
10
- parser.add_argument("--rowDistance", type=int, default=300)
11
- parser.add_argument("--columnDistance", type=int, default=300)
12
- parser.add_argument("--intersectionWidth", type=int, default=30)
13
- parser.add_argument("--numLeftLanes", type=int, default=1)
14
- parser.add_argument("--numStraightLanes", type=int, default=1)
15
- parser.add_argument("--numRightLanes", type=int, default=1)
16
- parser.add_argument("--laneMaxSpeed", type=float, default=16.67)
17
- parser.add_argument("--vehLen", type=float, default=5.0)
18
- parser.add_argument("--vehWidth", type=float, default=2.0)
19
- parser.add_argument("--vehMaxPosAcc", type=float, default=2.0)
20
- parser.add_argument("--vehMaxNegAcc", type=float, default=4.5)
21
- parser.add_argument("--vehUsualPosAcc", type=float, default=2.0)
22
- parser.add_argument("--vehUsualNegAcc", type=float, default=4.5)
23
- parser.add_argument("--vehMinGap", type=float, default=2.5)
24
- parser.add_argument("--vehMaxSpeed", type=float, default=16.67)
25
- parser.add_argument("--vehHeadwayTime", type=float, default=1.5)
26
- parser.add_argument("--dir", type=str, default="./")
27
- parser.add_argument("--roadnetFile", type=str)
28
- parser.add_argument("--turn", action="store_true")
29
- parser.add_argument("--tlPlan", action="store_true")
30
- parser.add_argument("--interval", type=float, default=2.0)
31
- parser.add_argument("--flowFile", type=str)
32
- return parser.parse_args()
33
-
34
- def generate_route(rowNum, colNum, turn=False):
35
- routes = []
36
- move = [(1, 0), (0, 1), (-1, 0), (0, -1)]
37
-
38
- def get_straight_route(start, direction, step):
39
- x, y = start
40
- route = []
41
- for _ in range(step):
42
- route.append("road_%d_%d_%d" % (x, y, direction))
43
- x += move[direction][0]
44
- y += move[direction][1]
45
- return route
46
-
47
- for i in range(1, rowNum+1):
48
- routes.append(get_straight_route((0, i), 0, colNum+1))
49
- routes.append(get_straight_route((colNum+1, i), 2, colNum+1))
50
- for i in range(1, colNum+1):
51
- routes.append(get_straight_route((i, 0), 1, rowNum+1))
52
- routes.append(get_straight_route((i, rowNum+1), 3, rowNum+1))
53
-
54
- if turn:
55
- def get_turn_route(start, direction):
56
- if direction[0] % 2 == 0:
57
- step = min(rowNum*2, colNum*2+1)
58
- else:
59
- step = min(colNum*2, rowNum*2+1)
60
- x, y = start
61
- route = []
62
- cur = 0
63
- for _ in range(step):
64
- route.append("road_%d_%d_%d" % (x, y, direction[cur]))
65
- x += move[direction[cur]][0]
66
- y += move[direction[cur]][1]
67
- cur = 1 - cur
68
- return route
69
-
70
- routes.append(get_turn_route((1, 0), (1, 0)))
71
- routes.append(get_turn_route((0, 1), (0, 1)))
72
- routes.append(get_turn_route((colNum+1, rowNum), (2, 3)))
73
- routes.append(get_turn_route((colNum, rowNum+1), (3, 2)))
74
- routes.append(get_turn_route((0, rowNum), (0, 3)))
75
- routes.append(get_turn_route((1, rowNum+1), (3, 0)))
76
- routes.append(get_turn_route((colNum+1, 1), (2, 1)))
77
- routes.append(get_turn_route((colNum, 0), (1, 2)))
78
-
79
- return routes
80
-
81
- if __name__ == '__main__':
82
- args = parse_args()
83
- if args.roadnetFile is None:
84
- args.roadnetFile = "roadnet_%d_%d%s.json" % (args.rowNum, args.colNum, "_turn" if args.turn else "")
85
- if args.flowFile is None:
86
- args.flowFile = "flow_%d_%d%s.json" % (args.rowNum, args.colNum, "_turn" if args.turn else "")
87
-
88
- grid = {
89
- "rowNumber": args.rowNum,
90
- "columnNumber": args.colNum,
91
- "rowDistances": [args.rowDistance] * (args.colNum-1),
92
- "columnDistances": [args.columnDistance] * (args.rowNum-1),
93
- "outRowDistance": args.rowDistance,
94
- "outColumnDistance": args.columnDistance,
95
- "intersectionWidths": [[args.intersectionWidth] * args.colNum] * args.rowNum,
96
- "numLeftLanes": args.numLeftLanes,
97
- "numStraightLanes": args.numStraightLanes,
98
- "numRightLanes": args.numRightLanes,
99
- "laneMaxSpeed": args.laneMaxSpeed,
100
- "tlPlan": args.tlPlan
101
- }
102
-
103
- json.dump(gridToRoadnet(**grid), open(os.path.join(args.dir, args.roadnetFile), "w"), indent=2)
104
-
105
- vehicle_template = {
106
- "length": args.vehLen,
107
- "width": args.vehWidth,
108
- "maxPosAcc": args.vehMaxPosAcc,
109
- "maxNegAcc": args.vehMaxNegAcc,
110
- "usualPosAcc": args.vehUsualPosAcc,
111
- "usualNegAcc": args.vehUsualNegAcc,
112
- "minGap": args.vehMinGap,
113
- "maxSpeed": args.vehMaxSpeed,
114
- "headwayTime": args.vehHeadwayTime
115
- }
116
- routes = generate_route(args.rowNum, args.colNum, args.turn)
117
- flow = []
118
- for route in routes:
119
- flow.append({
120
- "vehicle": vehicle_template,
121
- "route": route,
122
- "interval": args.interval,
123
- "startTime": 0,
124
- "endTime": -1
125
- })
126
- json.dump(flow, open(os.path.join(args.dir, args.flowFile), "w"), indent=2)
127
-
 
1
+ import argparse
2
+ import json
3
+ import os
4
+ from generate_json_from_grid import gridToRoadnet
5
+
6
+ def parse_args():
7
+ parser = argparse.ArgumentParser()
8
+ parser.add_argument("rowNum", type=int)
9
+ parser.add_argument("colNum", type=int)
10
+ parser.add_argument("--rowDistance", type=int, default=300)
11
+ parser.add_argument("--columnDistance", type=int, default=300)
12
+ parser.add_argument("--intersectionWidth", type=int, default=30)
13
+ parser.add_argument("--numLeftLanes", type=int, default=1)
14
+ parser.add_argument("--numStraightLanes", type=int, default=1)
15
+ parser.add_argument("--numRightLanes", type=int, default=1)
16
+ parser.add_argument("--laneMaxSpeed", type=float, default=16.67)
17
+ parser.add_argument("--vehLen", type=float, default=5.0)
18
+ parser.add_argument("--vehWidth", type=float, default=2.0)
19
+ parser.add_argument("--vehMaxPosAcc", type=float, default=2.0)
20
+ parser.add_argument("--vehMaxNegAcc", type=float, default=4.5)
21
+ parser.add_argument("--vehUsualPosAcc", type=float, default=2.0)
22
+ parser.add_argument("--vehUsualNegAcc", type=float, default=4.5)
23
+ parser.add_argument("--vehMinGap", type=float, default=2.5)
24
+ parser.add_argument("--vehMaxSpeed", type=float, default=16.67)
25
+ parser.add_argument("--vehHeadwayTime", type=float, default=1.5)
26
+ parser.add_argument("--dir", type=str, default="./")
27
+ parser.add_argument("--roadnetFile", type=str)
28
+ parser.add_argument("--turn", action="store_true")
29
+ parser.add_argument("--tlPlan", action="store_true")
30
+ parser.add_argument("--interval", type=float, default=2.0)
31
+ parser.add_argument("--flowFile", type=str)
32
+ return parser.parse_args()
33
+
34
+ def generate_route(rowNum, colNum, turn=False):
35
+ routes = []
36
+ move = [(1, 0), (0, 1), (-1, 0), (0, -1)]
37
+
38
+ def get_straight_route(start, direction, step):
39
+ x, y = start
40
+ route = []
41
+ for _ in range(step):
42
+ route.append("road_%d_%d_%d" % (x, y, direction))
43
+ x += move[direction][0]
44
+ y += move[direction][1]
45
+ return route
46
+
47
+ for i in range(1, rowNum+1):
48
+ routes.append(get_straight_route((0, i), 0, colNum+1))
49
+ routes.append(get_straight_route((colNum+1, i), 2, colNum+1))
50
+ for i in range(1, colNum+1):
51
+ routes.append(get_straight_route((i, 0), 1, rowNum+1))
52
+ routes.append(get_straight_route((i, rowNum+1), 3, rowNum+1))
53
+
54
+ if turn:
55
+ def get_turn_route(start, direction):
56
+ if direction[0] % 2 == 0:
57
+ step = min(rowNum*2, colNum*2+1)
58
+ else:
59
+ step = min(colNum*2, rowNum*2+1)
60
+ x, y = start
61
+ route = []
62
+ cur = 0
63
+ for _ in range(step):
64
+ route.append("road_%d_%d_%d" % (x, y, direction[cur]))
65
+ x += move[direction[cur]][0]
66
+ y += move[direction[cur]][1]
67
+ cur = 1 - cur
68
+ return route
69
+
70
+ routes.append(get_turn_route((1, 0), (1, 0)))
71
+ routes.append(get_turn_route((0, 1), (0, 1)))
72
+ routes.append(get_turn_route((colNum+1, rowNum), (2, 3)))
73
+ routes.append(get_turn_route((colNum, rowNum+1), (3, 2)))
74
+ routes.append(get_turn_route((0, rowNum), (0, 3)))
75
+ routes.append(get_turn_route((1, rowNum+1), (3, 0)))
76
+ routes.append(get_turn_route((colNum+1, 1), (2, 1)))
77
+ routes.append(get_turn_route((colNum, 0), (1, 2)))
78
+
79
+ return routes
80
+
81
+ if __name__ == '__main__':
82
+ args = parse_args()
83
+ if args.roadnetFile is None:
84
+ args.roadnetFile = "roadnet_%d_%d%s.json" % (args.rowNum, args.colNum, "_turn" if args.turn else "")
85
+ if args.flowFile is None:
86
+ args.flowFile = "flow_%d_%d%s.json" % (args.rowNum, args.colNum, "_turn" if args.turn else "")
87
+
88
+ grid = {
89
+ "rowNumber": args.rowNum,
90
+ "columnNumber": args.colNum,
91
+ "rowDistances": [args.rowDistance] * (args.colNum-1),
92
+ "columnDistances": [args.columnDistance] * (args.rowNum-1),
93
+ "outRowDistance": args.rowDistance,
94
+ "outColumnDistance": args.columnDistance,
95
+ "intersectionWidths": [[args.intersectionWidth] * args.colNum] * args.rowNum,
96
+ "numLeftLanes": args.numLeftLanes,
97
+ "numStraightLanes": args.numStraightLanes,
98
+ "numRightLanes": args.numRightLanes,
99
+ "laneMaxSpeed": args.laneMaxSpeed,
100
+ "tlPlan": args.tlPlan
101
+ }
102
+
103
+ json.dump(gridToRoadnet(**grid), open(os.path.join(args.dir, args.roadnetFile), "w"), indent=2)
104
+
105
+ vehicle_template = {
106
+ "length": args.vehLen,
107
+ "width": args.vehWidth,
108
+ "maxPosAcc": args.vehMaxPosAcc,
109
+ "maxNegAcc": args.vehMaxNegAcc,
110
+ "usualPosAcc": args.vehUsualPosAcc,
111
+ "usualNegAcc": args.vehUsualNegAcc,
112
+ "minGap": args.vehMinGap,
113
+ "maxSpeed": args.vehMaxSpeed,
114
+ "headwayTime": args.vehHeadwayTime
115
+ }
116
+ routes = generate_route(args.rowNum, args.colNum, args.turn)
117
+ flow = []
118
+ for route in routes:
119
+ flow.append({
120
+ "vehicle": vehicle_template,
121
+ "route": route,
122
+ "interval": args.interval,
123
+ "startTime": 0,
124
+ "endTime": -1
125
+ })
126
+ json.dump(flow, open(os.path.join(args.dir, args.flowFile), "w"), indent=2)
127
+
third_party/CityFlow/tools/generator/generate_json_from_grid.py CHANGED
@@ -1,428 +1,428 @@
1
- from __future__ import division, print_function
2
- import sys
3
- import argparse
4
- import json
5
- import math
6
- import numpy as np
7
-
8
- parser = argparse.ArgumentParser(description='Generate roadnet JSON from a grid\
9
- network JSON file.')
10
- parser.add_argument('--config', dest='config', help='grid config file', type=str)
11
-
12
- dx = [1, 0, -1, 0]
13
- dy = [0, 1, 0, -1]
14
-
15
- def isHorizontal(road):
16
- return dx[road["direction"]] != 0
17
-
18
- def isVertical(k):
19
- return dy[k] == 0
20
-
21
- def pointToDict(x, y):
22
- return {"x": x, "y": y}
23
-
24
- def pointToDict2(p):
25
- return {"x": float(p.x), "y": float(p.y)}
26
-
27
- def pointToDict3(p):
28
- return {"x": p[0], "y": p[1]}
29
-
30
- def getLaneShift(road, laneIndex):
31
- shift = 0.
32
- for i in range(laneIndex):
33
- shift += road["lanes"][i]["width"]
34
- shift += road["lanes"][laneIndex]["width"] * .5
35
- return shift
36
-
37
- def getRoadUnitVector(road):
38
- startPoint = road["points"][0]
39
- endPoint = road["points"][-1]
40
- dx = endPoint["x"] - startPoint["x"]
41
- dy = endPoint["y"] - startPoint["y"]
42
- length = math.sqrt(dx * dx + dy * dy)
43
- # print(length, dx, dy)
44
- return dx / length, dy / length
45
-
46
- def getOutPoint(road, width, laneIndex):
47
- dx, dy = getRoadUnitVector(road)
48
- laneShift = getLaneShift(road, laneIndex)
49
- point = road["points"][-1]
50
- x, y = point["x"], point["y"]
51
- x, y = x - dx * width, y - dy * width
52
- x, y = x + dy * laneShift, y - dx * laneShift
53
- return x, y
54
-
55
- def getOutTurnPoints(road, lim, laneIndex, width):
56
- dx, dy = getRoadUnitVector(road)
57
- laneShift = getLaneShift(road, laneIndex)
58
- # distance = width * .5
59
- point = road["points"][-1]
60
- x, y = point["x"], point["y"]
61
- x, y = x - dx * width, y - dy * width
62
- x, y = x + dy * laneShift, y - dx * laneShift
63
- return [pointToDict(x, y),
64
- pointToDict(x + dx * lim, y + dy * lim)]
65
-
66
- def getInPoint(road, width, laneIndex):
67
- dx, dy = getRoadUnitVector(road)
68
- laneShift = getLaneShift(road, laneIndex)
69
- point = road["points"][0]
70
- x, y = point["x"], point["y"]
71
- x, y = x + dx * width, y + dy * width
72
- x, y = x + dy * laneShift, y - dx * laneShift
73
- return x, y
74
-
75
- def getInTurnPoints(road, lim, laneIndex, width):
76
- dx, dy = getRoadUnitVector(road)
77
- laneShift = getLaneShift(road, laneIndex)
78
- # distance = width * .5
79
- point = road["points"][0]
80
- x, y = point["x"], point["y"]
81
- x, y = x + dx * width, y + dy * width
82
- x, y = x + dy * laneShift, y - dx * laneShift
83
- return [pointToDict(x - dx * lim, y - dy * lim),
84
- pointToDict(x, y),]
85
-
86
- # Hermite Spline
87
- def findPath(roada, lanea, roadb, laneb, width, midPoint=10):
88
- # def scale(k, p):
89
- # return p.scale(k, k)
90
-
91
- dxa, dya = getRoadUnitVector(roada)
92
- dxb, dyb = getRoadUnitVector(roadb)
93
- pxa, pya = getOutPoint(roada, width, lanea)
94
- pxb, pyb = getInPoint(roadb, width, laneb)
95
-
96
- # pa = Point(pxa, pya)
97
- # pb = Point(pxb, pyb)
98
- dxa = dxa * width
99
- dya = dya * width
100
- dxb = dxb * width
101
- dyb = dyb * width
102
- # da = scale(width, Point(dxa, dya))
103
- # db = scale(width, Point(dxb, dyb))
104
-
105
- path = []
106
- for i in range(midPoint + 1):
107
- t = i / midPoint
108
- t3 = t * t * t
109
- t2 = t * t
110
-
111
- k1 = 2 * t3 - 3 * t2 + 1
112
- x1 = k1 * pxa
113
- y1 = k1 * pya
114
-
115
- k2 = t3 - 2 * t2 + t
116
- x2 = k2 * dxa
117
- y2 = k2 * dya
118
-
119
- k3 = -2 * t3 + 3 * t2
120
- x3 = k3 * pxb
121
- y3 = k3 * pyb
122
-
123
- k4 = t3 - t2
124
- x4 = k4 * dxb
125
- y4 = k4 * dyb
126
-
127
- path.append([x1 + x2 + x3 + x4, y1 + y2 + y3 + y4])
128
- # path.append((scale(2 * t3 - 3 * t2 + 1, pa) +
129
- # scale(t3 - 2 * t2 + t, da) +
130
- # scale(-2 * t3 + 3 * t2, pb) +
131
- # scale(t3 - t2, db)).evalf())
132
- # path.append(pb)
133
-
134
- # print(path)
135
-
136
- return list(map(pointToDict3, path))
137
-
138
- def findPathSimple(roada, lanea, roadb, laneb, width):
139
- dxa, dya = getRoadUnitVector(roada)
140
- dxb, dyb = getRoadUnitVector(roadb)
141
- pxa, pya = getOutPoint(roada, width, lanea)
142
- pxb, pyb = getInPoint(roadb, width, laneb)
143
-
144
- return [pointToDict(pxa, pya), pointToDict(pxa + dxa * width / 2, pya + dya * width / 2),
145
- pointToDict(pxb - dxb * width / 2, pyb - dyb * width / 2), pointToDict(pxb, pyb)]
146
-
147
- def decideType(roada, roadb):
148
- da = roada["direction"]
149
- db = roadb["direction"]
150
- if (da + 1) % 4 == db:
151
- return "turn_left"
152
- elif (db + 1) % 4 == da:
153
- return "turn_right"
154
- elif da == db:
155
- return "go_straight"
156
- else:
157
- raise ValueError
158
-
159
- def checkIntersection(interseciontName, isTruelyInside):
160
- return interseciontName
161
- # _, i, j = interseciontName.split('_')
162
- # i, j = int(i), int(j)
163
- # if isTruelyInside(i, j):
164
- # return interseciontName
165
- # else:
166
- # return None
167
-
168
- def gridToRoadnet(rowNumber, columnNumber, rowDistances, columnDistances, outRowDistance, outColumnDistance,
169
- intersectionWidths, laneWidth=4, laneMaxSpeed=20,
170
- numLeftLanes=1, numStraightLanes=1, numRightLanes=1, tlPlan=False, midPoints=10):
171
-
172
- rowNumber += 2
173
- columnNumber += 2
174
- numLanes = numLeftLanes + numStraightLanes + numRightLanes
175
- intersectionWidths = [[0] * columnNumber] + intersectionWidths + [[0] * columnNumber]
176
- for i in range(1, rowNumber - 1):
177
- intersectionWidths[i] = [0] + intersectionWidths[i] + [0]
178
-
179
- def isInside(i, j):
180
- return i >= 0 and j >= 0 and i < rowNumber and j < columnNumber
181
-
182
- def isTruelyInside(i, j):
183
- return i > 0 and j > 0 and i < rowNumber - 1 and j < columnNumber - 1
184
-
185
- def isCorner(i, j):
186
- return (i == 0 or i == rowNumber - 1) and (j == 0 or j == columnNumber - 1)
187
-
188
- def isEdge(i, j):
189
- return isInside(i, j) and not isTruelyInside(i, j)
190
-
191
- def shouldDraw(road):
192
- return isTruelyInside(road["fromi"], road["fromj"]) or isTruelyInside(road["toi"], road["toj"])
193
-
194
- def isLeftLane(index):
195
- return 0 <= index < numLeftLanes
196
-
197
- def isStraightLane(index):
198
- return numLeftLanes <= index < numLeftLanes + numStraightLanes
199
-
200
- def isRightLane(index):
201
- return numLeftLanes + numStraightLanes <= index < numLanes
202
-
203
- x = [[None for _ in range(columnNumber)] for _ in range(rowNumber)]
204
- y = [[None for _ in range(columnNumber)] for _ in range(rowNumber)]
205
-
206
- rowDistances = [outRowDistance] + rowDistances + [outRowDistance]
207
- columnDistances = [outColumnDistance] + columnDistances + [outColumnDistance]
208
-
209
- for i in range(rowNumber):
210
- for j in range(columnNumber):
211
- if j > 0:
212
- x[i][j] = x[i][j - 1] + rowDistances[i - 1]
213
- y[i][j] = y[i][j - 1]
214
- elif i > 0:
215
- x[i][j] = x[i - 1][j]
216
- y[i][j] = y[i - 1][j] + columnDistances[j - 1]
217
- else:
218
- x[i][j] = -outRowDistance
219
- y[i][j] = -outColumnDistance
220
-
221
- roads = [[[None, None, None, None] for _ in range(columnNumber)] for _ in range(rowNumber)]
222
- for i in range(rowNumber):
223
- for j in range(columnNumber):
224
- for k in range(4):
225
- ni, nj = i + dy[k], j + dx[k]
226
- if not isInside(ni, nj):
227
- road = None
228
- else:
229
- road = {
230
- "id": "road_%d_%d_%d" % (j, i, k),
231
- "direction": k,
232
- "fromi": i,
233
- "fromj": j,
234
- "toi": ni,
235
- "toj": nj,
236
- "points": [
237
- pointToDict(x[i][j], y[i][j]),
238
- pointToDict(x[ni][nj], y[ni][nj])
239
- ],
240
- "lanes": [
241
- {
242
- "width": laneWidth,
243
- "maxSpeed": laneMaxSpeed
244
- }
245
- ] * numLanes,
246
- "startIntersection": "intersection_%d_%d" % (j, i),
247
- "endIntersection": "intersection_%d_%d" % (nj, ni)
248
- }
249
- roads[i][j][k] = road
250
-
251
- intersections = [[None for _ in range(columnNumber)] for _ in range(rowNumber)]
252
- for i in range(rowNumber):
253
- for j in range(columnNumber):
254
- width = intersectionWidths[i][j]
255
- intersection = {
256
- "id": "intersection_%d_%d" % (j, i),
257
- "point": pointToDict(x[i][j], y[i][j]),
258
- "width": width,
259
- "roads": [],
260
- "roadLinks": [],
261
- "trafficLight": {
262
- "roadLinkIndices": [],
263
- "lightphases": []
264
- },
265
- "virtual": not isTruelyInside(i, j)
266
- }
267
-
268
- roadLinks = intersection["roadLinks"]
269
- roadLinkIndices = intersection["trafficLight"]["roadLinkIndices"]
270
-
271
- outRoads = list(filter(lambda x: x is not None, roads[i][j]))
272
- inRoads = list(map(lambda k: roads[i - dy[k]][j - dx[k]][k],
273
- filter(lambda k: isInside(i - dy[k], j - dx[k]), range(4))))
274
- outRoads = list(filter(shouldDraw, outRoads))
275
- inRoads = list(filter(shouldDraw, inRoads))
276
-
277
- for road in inRoads + outRoads:
278
- intersection["roads"].append(road["id"])
279
-
280
- for a in range(len(inRoads)):
281
- for b in range(len(outRoads)):
282
- try:
283
- roada = inRoads[a]
284
- roadb = outRoads[b]
285
- roadLink = {
286
- "type": decideType(roada, roadb),
287
- "startRoad": roada["id"],
288
- "endRoad": roadb["id"],
289
- "direction": roada["direction"],
290
- "laneLinks": []
291
- }
292
- for c in range(len(roada["lanes"])):
293
- if roadLink["type"] == "turn_left" and not isLeftLane(c):
294
- continue
295
- if roadLink["type"] == "go_straight" and not isStraightLane(c):
296
- continue
297
- if roadLink["type"] == "turn_right" and not isRightLane(c):
298
- continue
299
- for d in range(len(roadb["lanes"])):
300
- path = {
301
- "startLaneIndex": c,
302
- "endLaneIndex": d,
303
- "points": findPath(roada, c, roadb, d, width, midPoints)
304
- }
305
- roadLink["laneLinks"].append(path)
306
- if roadLink["laneLinks"]:
307
- roadLinkIndices.append(len(roadLinks))
308
- roadLinks.append(roadLink)
309
- except ValueError:
310
- pass
311
-
312
- leftLaneLinks = set(filter(lambda x: roadLinks[x]["type"] == "turn_left", roadLinkIndices))
313
- rightLaneLinks = set(filter(lambda x: roadLinks[x]["type"] == "turn_right", roadLinkIndices))
314
- straightLaneLinks = set(filter(lambda x: roadLinks[x]["type"] == "go_straight", roadLinkIndices))
315
- WELaneLinks = set(filter(lambda x: roadLinks[x]["direction"] == 0, roadLinkIndices))
316
- NSLaneLinks = set(filter(lambda x: roadLinks[x]["direction"] == 1, roadLinkIndices))
317
- EWLaneLinks = set(filter(lambda x: roadLinks[x]["direction"] == 2, roadLinkIndices))
318
- SNLaneLinks = set(filter(lambda x: roadLinks[x]["direction"] == 3, roadLinkIndices))
319
-
320
- tlPhases = intersection["trafficLight"]["lightphases"]
321
- if not tlPlan:
322
- tlPhases.append({
323
- "time": 5,
324
- "availableRoadLinks": rightLaneLinks
325
- })
326
- tlPhases.append({
327
- "time": 30,
328
- "availableRoadLinks": ((EWLaneLinks | WELaneLinks) & straightLaneLinks) | (rightLaneLinks)
329
- })
330
- tlPhases.append({
331
- "time": 30,
332
- "availableRoadLinks": ((NSLaneLinks | SNLaneLinks) & straightLaneLinks) | (rightLaneLinks)
333
- })
334
- tlPhases.append({
335
- "time": 30,
336
- "availableRoadLinks": ((EWLaneLinks | WELaneLinks) & leftLaneLinks) | (rightLaneLinks)
337
- })
338
- tlPhases.append({
339
- "time": 30,
340
- "availableRoadLinks": ((SNLaneLinks | NSLaneLinks) & leftLaneLinks) | (rightLaneLinks)
341
- })
342
- tlPhases.append({
343
- "time": 30,
344
- "availableRoadLinks": (WELaneLinks) | (rightLaneLinks)
345
- })
346
- tlPhases.append({
347
- "time": 30,
348
- "availableRoadLinks": (EWLaneLinks) | (rightLaneLinks)
349
- })
350
- tlPhases.append({
351
- "time": 30,
352
- "availableRoadLinks": (NSLaneLinks) | (rightLaneLinks)
353
- })
354
- tlPhases.append({
355
- "time": 30,
356
- "availableRoadLinks": (SNLaneLinks) | (rightLaneLinks)
357
- })
358
- else:
359
- tlPhases.append({
360
- "time": 30,
361
- "availableRoadLinks": ((EWLaneLinks | WELaneLinks) & straightLaneLinks) | (rightLaneLinks)
362
- })
363
- tlPhases.append({
364
- "time": 5,
365
- "availableRoadLinks": rightLaneLinks
366
- })
367
- if numLeftLanes:
368
- tlPhases.append({
369
- "time": 30,
370
- "availableRoadLinks": ((EWLaneLinks | WELaneLinks) & leftLaneLinks) | (rightLaneLinks)
371
- })
372
- tlPhases.append({
373
- "time": 5,
374
- "availableRoadLinks": rightLaneLinks
375
- })
376
- tlPhases.append({
377
- "time": 30,
378
- "availableRoadLinks": ((NSLaneLinks | SNLaneLinks) & straightLaneLinks) | (rightLaneLinks)
379
- })
380
- tlPhases.append({
381
- "time": 5,
382
- "availableRoadLinks": rightLaneLinks
383
- })
384
- if numLeftLanes:
385
- tlPhases.append({
386
- "time": 30,
387
- "availableRoadLinks": ((SNLaneLinks | NSLaneLinks) & leftLaneLinks) | (rightLaneLinks)
388
- })
389
- tlPhases.append({
390
- "time": 5,
391
- "availableRoadLinks": rightLaneLinks
392
- })
393
- for tlPhase in tlPhases:
394
- tlPhase["availableRoadLinks"] = list(tlPhase["availableRoadLinks"])
395
- intersections[i][j] = intersection
396
-
397
- final_intersecions = []
398
- for i in range(rowNumber):
399
- for j in range(columnNumber):
400
- if not isCorner(i, j):
401
- # print(i, j)
402
- final_intersecions.append(intersections[i][j])
403
-
404
- final_roads = []
405
- for i in range(rowNumber):
406
- for j in range(columnNumber):
407
- for k in range(4):
408
- if roads[i][j][k] is not None and shouldDraw(roads[i][j][k]):
409
- ni, nj = i + dy[k], j + dx[k]
410
- roads[i][j][k].pop("direction")
411
- roads[i][j][k].pop("fromi")
412
- roads[i][j][k].pop("fromj")
413
- roads[i][j][k].pop("toi")
414
- roads[i][j][k].pop("toj")
415
- roads[i][j][k]["startIntersection"] = checkIntersection(roads[i][j][k]["startIntersection"], isTruelyInside)
416
- roads[i][j][k]["endIntersection"] = checkIntersection(roads[i][j][k]["endIntersection"], isTruelyInside)
417
- final_roads.append(roads[i][j][k])
418
-
419
- return {
420
- "intersections": final_intersecions,
421
- "roads": final_roads
422
- }
423
-
424
- if __name__ == "__main__":
425
- args = parser.parse_args()
426
- path = '../data/roadnet/'
427
- grid_config = json.load(open(path + args.config if args.config is not None else path + 'grid66.json'))
428
- json.dump(gridToRoadnet(**grid_config), open(path + "roadnet66.json", "w"), indent=2)
 
1
+ from __future__ import division, print_function
2
+ import sys
3
+ import argparse
4
+ import json
5
+ import math
6
+ import numpy as np
7
+
8
+ parser = argparse.ArgumentParser(description='Generate roadnet JSON from a grid\
9
+ network JSON file.')
10
+ parser.add_argument('--config', dest='config', help='grid config file', type=str)
11
+
12
+ dx = [1, 0, -1, 0]
13
+ dy = [0, 1, 0, -1]
14
+
15
+ def isHorizontal(road):
16
+ return dx[road["direction"]] != 0
17
+
18
+ def isVertical(k):
19
+ return dy[k] == 0
20
+
21
+ def pointToDict(x, y):
22
+ return {"x": x, "y": y}
23
+
24
+ def pointToDict2(p):
25
+ return {"x": float(p.x), "y": float(p.y)}
26
+
27
+ def pointToDict3(p):
28
+ return {"x": p[0], "y": p[1]}
29
+
30
+ def getLaneShift(road, laneIndex):
31
+ shift = 0.
32
+ for i in range(laneIndex):
33
+ shift += road["lanes"][i]["width"]
34
+ shift += road["lanes"][laneIndex]["width"] * .5
35
+ return shift
36
+
37
+ def getRoadUnitVector(road):
38
+ startPoint = road["points"][0]
39
+ endPoint = road["points"][-1]
40
+ dx = endPoint["x"] - startPoint["x"]
41
+ dy = endPoint["y"] - startPoint["y"]
42
+ length = math.sqrt(dx * dx + dy * dy)
43
+ # print(length, dx, dy)
44
+ return dx / length, dy / length
45
+
46
+ def getOutPoint(road, width, laneIndex):
47
+ dx, dy = getRoadUnitVector(road)
48
+ laneShift = getLaneShift(road, laneIndex)
49
+ point = road["points"][-1]
50
+ x, y = point["x"], point["y"]
51
+ x, y = x - dx * width, y - dy * width
52
+ x, y = x + dy * laneShift, y - dx * laneShift
53
+ return x, y
54
+
55
+ def getOutTurnPoints(road, lim, laneIndex, width):
56
+ dx, dy = getRoadUnitVector(road)
57
+ laneShift = getLaneShift(road, laneIndex)
58
+ # distance = width * .5
59
+ point = road["points"][-1]
60
+ x, y = point["x"], point["y"]
61
+ x, y = x - dx * width, y - dy * width
62
+ x, y = x + dy * laneShift, y - dx * laneShift
63
+ return [pointToDict(x, y),
64
+ pointToDict(x + dx * lim, y + dy * lim)]
65
+
66
+ def getInPoint(road, width, laneIndex):
67
+ dx, dy = getRoadUnitVector(road)
68
+ laneShift = getLaneShift(road, laneIndex)
69
+ point = road["points"][0]
70
+ x, y = point["x"], point["y"]
71
+ x, y = x + dx * width, y + dy * width
72
+ x, y = x + dy * laneShift, y - dx * laneShift
73
+ return x, y
74
+
75
+ def getInTurnPoints(road, lim, laneIndex, width):
76
+ dx, dy = getRoadUnitVector(road)
77
+ laneShift = getLaneShift(road, laneIndex)
78
+ # distance = width * .5
79
+ point = road["points"][0]
80
+ x, y = point["x"], point["y"]
81
+ x, y = x + dx * width, y + dy * width
82
+ x, y = x + dy * laneShift, y - dx * laneShift
83
+ return [pointToDict(x - dx * lim, y - dy * lim),
84
+ pointToDict(x, y),]
85
+
86
+ # Hermite Spline
87
+ def findPath(roada, lanea, roadb, laneb, width, midPoint=10):
88
+ # def scale(k, p):
89
+ # return p.scale(k, k)
90
+
91
+ dxa, dya = getRoadUnitVector(roada)
92
+ dxb, dyb = getRoadUnitVector(roadb)
93
+ pxa, pya = getOutPoint(roada, width, lanea)
94
+ pxb, pyb = getInPoint(roadb, width, laneb)
95
+
96
+ # pa = Point(pxa, pya)
97
+ # pb = Point(pxb, pyb)
98
+ dxa = dxa * width
99
+ dya = dya * width
100
+ dxb = dxb * width
101
+ dyb = dyb * width
102
+ # da = scale(width, Point(dxa, dya))
103
+ # db = scale(width, Point(dxb, dyb))
104
+
105
+ path = []
106
+ for i in range(midPoint + 1):
107
+ t = i / midPoint
108
+ t3 = t * t * t
109
+ t2 = t * t
110
+
111
+ k1 = 2 * t3 - 3 * t2 + 1
112
+ x1 = k1 * pxa
113
+ y1 = k1 * pya
114
+
115
+ k2 = t3 - 2 * t2 + t
116
+ x2 = k2 * dxa
117
+ y2 = k2 * dya
118
+
119
+ k3 = -2 * t3 + 3 * t2
120
+ x3 = k3 * pxb
121
+ y3 = k3 * pyb
122
+
123
+ k4 = t3 - t2
124
+ x4 = k4 * dxb
125
+ y4 = k4 * dyb
126
+
127
+ path.append([x1 + x2 + x3 + x4, y1 + y2 + y3 + y4])
128
+ # path.append((scale(2 * t3 - 3 * t2 + 1, pa) +
129
+ # scale(t3 - 2 * t2 + t, da) +
130
+ # scale(-2 * t3 + 3 * t2, pb) +
131
+ # scale(t3 - t2, db)).evalf())
132
+ # path.append(pb)
133
+
134
+ # print(path)
135
+
136
+ return list(map(pointToDict3, path))
137
+
138
+ def findPathSimple(roada, lanea, roadb, laneb, width):
139
+ dxa, dya = getRoadUnitVector(roada)
140
+ dxb, dyb = getRoadUnitVector(roadb)
141
+ pxa, pya = getOutPoint(roada, width, lanea)
142
+ pxb, pyb = getInPoint(roadb, width, laneb)
143
+
144
+ return [pointToDict(pxa, pya), pointToDict(pxa + dxa * width / 2, pya + dya * width / 2),
145
+ pointToDict(pxb - dxb * width / 2, pyb - dyb * width / 2), pointToDict(pxb, pyb)]
146
+
147
+ def decideType(roada, roadb):
148
+ da = roada["direction"]
149
+ db = roadb["direction"]
150
+ if (da + 1) % 4 == db:
151
+ return "turn_left"
152
+ elif (db + 1) % 4 == da:
153
+ return "turn_right"
154
+ elif da == db:
155
+ return "go_straight"
156
+ else:
157
+ raise ValueError
158
+
159
+ def checkIntersection(interseciontName, isTruelyInside):
160
+ return interseciontName
161
+ # _, i, j = interseciontName.split('_')
162
+ # i, j = int(i), int(j)
163
+ # if isTruelyInside(i, j):
164
+ # return interseciontName
165
+ # else:
166
+ # return None
167
+
168
+ def gridToRoadnet(rowNumber, columnNumber, rowDistances, columnDistances, outRowDistance, outColumnDistance,
169
+ intersectionWidths, laneWidth=4, laneMaxSpeed=20,
170
+ numLeftLanes=1, numStraightLanes=1, numRightLanes=1, tlPlan=False, midPoints=10):
171
+
172
+ rowNumber += 2
173
+ columnNumber += 2
174
+ numLanes = numLeftLanes + numStraightLanes + numRightLanes
175
+ intersectionWidths = [[0] * columnNumber] + intersectionWidths + [[0] * columnNumber]
176
+ for i in range(1, rowNumber - 1):
177
+ intersectionWidths[i] = [0] + intersectionWidths[i] + [0]
178
+
179
+ def isInside(i, j):
180
+ return i >= 0 and j >= 0 and i < rowNumber and j < columnNumber
181
+
182
+ def isTruelyInside(i, j):
183
+ return i > 0 and j > 0 and i < rowNumber - 1 and j < columnNumber - 1
184
+
185
+ def isCorner(i, j):
186
+ return (i == 0 or i == rowNumber - 1) and (j == 0 or j == columnNumber - 1)
187
+
188
+ def isEdge(i, j):
189
+ return isInside(i, j) and not isTruelyInside(i, j)
190
+
191
+ def shouldDraw(road):
192
+ return isTruelyInside(road["fromi"], road["fromj"]) or isTruelyInside(road["toi"], road["toj"])
193
+
194
+ def isLeftLane(index):
195
+ return 0 <= index < numLeftLanes
196
+
197
+ def isStraightLane(index):
198
+ return numLeftLanes <= index < numLeftLanes + numStraightLanes
199
+
200
+ def isRightLane(index):
201
+ return numLeftLanes + numStraightLanes <= index < numLanes
202
+
203
+ x = [[None for _ in range(columnNumber)] for _ in range(rowNumber)]
204
+ y = [[None for _ in range(columnNumber)] for _ in range(rowNumber)]
205
+
206
+ rowDistances = [outRowDistance] + rowDistances + [outRowDistance]
207
+ columnDistances = [outColumnDistance] + columnDistances + [outColumnDistance]
208
+
209
+ for i in range(rowNumber):
210
+ for j in range(columnNumber):
211
+ if j > 0:
212
+ x[i][j] = x[i][j - 1] + rowDistances[i - 1]
213
+ y[i][j] = y[i][j - 1]
214
+ elif i > 0:
215
+ x[i][j] = x[i - 1][j]
216
+ y[i][j] = y[i - 1][j] + columnDistances[j - 1]
217
+ else:
218
+ x[i][j] = -outRowDistance
219
+ y[i][j] = -outColumnDistance
220
+
221
+ roads = [[[None, None, None, None] for _ in range(columnNumber)] for _ in range(rowNumber)]
222
+ for i in range(rowNumber):
223
+ for j in range(columnNumber):
224
+ for k in range(4):
225
+ ni, nj = i + dy[k], j + dx[k]
226
+ if not isInside(ni, nj):
227
+ road = None
228
+ else:
229
+ road = {
230
+ "id": "road_%d_%d_%d" % (j, i, k),
231
+ "direction": k,
232
+ "fromi": i,
233
+ "fromj": j,
234
+ "toi": ni,
235
+ "toj": nj,
236
+ "points": [
237
+ pointToDict(x[i][j], y[i][j]),
238
+ pointToDict(x[ni][nj], y[ni][nj])
239
+ ],
240
+ "lanes": [
241
+ {
242
+ "width": laneWidth,
243
+ "maxSpeed": laneMaxSpeed
244
+ }
245
+ ] * numLanes,
246
+ "startIntersection": "intersection_%d_%d" % (j, i),
247
+ "endIntersection": "intersection_%d_%d" % (nj, ni)
248
+ }
249
+ roads[i][j][k] = road
250
+
251
+ intersections = [[None for _ in range(columnNumber)] for _ in range(rowNumber)]
252
+ for i in range(rowNumber):
253
+ for j in range(columnNumber):
254
+ width = intersectionWidths[i][j]
255
+ intersection = {
256
+ "id": "intersection_%d_%d" % (j, i),
257
+ "point": pointToDict(x[i][j], y[i][j]),
258
+ "width": width,
259
+ "roads": [],
260
+ "roadLinks": [],
261
+ "trafficLight": {
262
+ "roadLinkIndices": [],
263
+ "lightphases": []
264
+ },
265
+ "virtual": not isTruelyInside(i, j)
266
+ }
267
+
268
+ roadLinks = intersection["roadLinks"]
269
+ roadLinkIndices = intersection["trafficLight"]["roadLinkIndices"]
270
+
271
+ outRoads = list(filter(lambda x: x is not None, roads[i][j]))
272
+ inRoads = list(map(lambda k: roads[i - dy[k]][j - dx[k]][k],
273
+ filter(lambda k: isInside(i - dy[k], j - dx[k]), range(4))))
274
+ outRoads = list(filter(shouldDraw, outRoads))
275
+ inRoads = list(filter(shouldDraw, inRoads))
276
+
277
+ for road in inRoads + outRoads:
278
+ intersection["roads"].append(road["id"])
279
+
280
+ for a in range(len(inRoads)):
281
+ for b in range(len(outRoads)):
282
+ try:
283
+ roada = inRoads[a]
284
+ roadb = outRoads[b]
285
+ roadLink = {
286
+ "type": decideType(roada, roadb),
287
+ "startRoad": roada["id"],
288
+ "endRoad": roadb["id"],
289
+ "direction": roada["direction"],
290
+ "laneLinks": []
291
+ }
292
+ for c in range(len(roada["lanes"])):
293
+ if roadLink["type"] == "turn_left" and not isLeftLane(c):
294
+ continue
295
+ if roadLink["type"] == "go_straight" and not isStraightLane(c):
296
+ continue
297
+ if roadLink["type"] == "turn_right" and not isRightLane(c):
298
+ continue
299
+ for d in range(len(roadb["lanes"])):
300
+ path = {
301
+ "startLaneIndex": c,
302
+ "endLaneIndex": d,
303
+ "points": findPath(roada, c, roadb, d, width, midPoints)
304
+ }
305
+ roadLink["laneLinks"].append(path)
306
+ if roadLink["laneLinks"]:
307
+ roadLinkIndices.append(len(roadLinks))
308
+ roadLinks.append(roadLink)
309
+ except ValueError:
310
+ pass
311
+
312
+ leftLaneLinks = set(filter(lambda x: roadLinks[x]["type"] == "turn_left", roadLinkIndices))
313
+ rightLaneLinks = set(filter(lambda x: roadLinks[x]["type"] == "turn_right", roadLinkIndices))
314
+ straightLaneLinks = set(filter(lambda x: roadLinks[x]["type"] == "go_straight", roadLinkIndices))
315
+ WELaneLinks = set(filter(lambda x: roadLinks[x]["direction"] == 0, roadLinkIndices))
316
+ NSLaneLinks = set(filter(lambda x: roadLinks[x]["direction"] == 1, roadLinkIndices))
317
+ EWLaneLinks = set(filter(lambda x: roadLinks[x]["direction"] == 2, roadLinkIndices))
318
+ SNLaneLinks = set(filter(lambda x: roadLinks[x]["direction"] == 3, roadLinkIndices))
319
+
320
+ tlPhases = intersection["trafficLight"]["lightphases"]
321
+ if not tlPlan:
322
+ tlPhases.append({
323
+ "time": 5,
324
+ "availableRoadLinks": rightLaneLinks
325
+ })
326
+ tlPhases.append({
327
+ "time": 30,
328
+ "availableRoadLinks": ((EWLaneLinks | WELaneLinks) & straightLaneLinks) | (rightLaneLinks)
329
+ })
330
+ tlPhases.append({
331
+ "time": 30,
332
+ "availableRoadLinks": ((NSLaneLinks | SNLaneLinks) & straightLaneLinks) | (rightLaneLinks)
333
+ })
334
+ tlPhases.append({
335
+ "time": 30,
336
+ "availableRoadLinks": ((EWLaneLinks | WELaneLinks) & leftLaneLinks) | (rightLaneLinks)
337
+ })
338
+ tlPhases.append({
339
+ "time": 30,
340
+ "availableRoadLinks": ((SNLaneLinks | NSLaneLinks) & leftLaneLinks) | (rightLaneLinks)
341
+ })
342
+ tlPhases.append({
343
+ "time": 30,
344
+ "availableRoadLinks": (WELaneLinks) | (rightLaneLinks)
345
+ })
346
+ tlPhases.append({
347
+ "time": 30,
348
+ "availableRoadLinks": (EWLaneLinks) | (rightLaneLinks)
349
+ })
350
+ tlPhases.append({
351
+ "time": 30,
352
+ "availableRoadLinks": (NSLaneLinks) | (rightLaneLinks)
353
+ })
354
+ tlPhases.append({
355
+ "time": 30,
356
+ "availableRoadLinks": (SNLaneLinks) | (rightLaneLinks)
357
+ })
358
+ else:
359
+ tlPhases.append({
360
+ "time": 30,
361
+ "availableRoadLinks": ((EWLaneLinks | WELaneLinks) & straightLaneLinks) | (rightLaneLinks)
362
+ })
363
+ tlPhases.append({
364
+ "time": 5,
365
+ "availableRoadLinks": rightLaneLinks
366
+ })
367
+ if numLeftLanes:
368
+ tlPhases.append({
369
+ "time": 30,
370
+ "availableRoadLinks": ((EWLaneLinks | WELaneLinks) & leftLaneLinks) | (rightLaneLinks)
371
+ })
372
+ tlPhases.append({
373
+ "time": 5,
374
+ "availableRoadLinks": rightLaneLinks
375
+ })
376
+ tlPhases.append({
377
+ "time": 30,
378
+ "availableRoadLinks": ((NSLaneLinks | SNLaneLinks) & straightLaneLinks) | (rightLaneLinks)
379
+ })
380
+ tlPhases.append({
381
+ "time": 5,
382
+ "availableRoadLinks": rightLaneLinks
383
+ })
384
+ if numLeftLanes:
385
+ tlPhases.append({
386
+ "time": 30,
387
+ "availableRoadLinks": ((SNLaneLinks | NSLaneLinks) & leftLaneLinks) | (rightLaneLinks)
388
+ })
389
+ tlPhases.append({
390
+ "time": 5,
391
+ "availableRoadLinks": rightLaneLinks
392
+ })
393
+ for tlPhase in tlPhases:
394
+ tlPhase["availableRoadLinks"] = list(tlPhase["availableRoadLinks"])
395
+ intersections[i][j] = intersection
396
+
397
+ final_intersecions = []
398
+ for i in range(rowNumber):
399
+ for j in range(columnNumber):
400
+ if not isCorner(i, j):
401
+ # print(i, j)
402
+ final_intersecions.append(intersections[i][j])
403
+
404
+ final_roads = []
405
+ for i in range(rowNumber):
406
+ for j in range(columnNumber):
407
+ for k in range(4):
408
+ if roads[i][j][k] is not None and shouldDraw(roads[i][j][k]):
409
+ ni, nj = i + dy[k], j + dx[k]
410
+ roads[i][j][k].pop("direction")
411
+ roads[i][j][k].pop("fromi")
412
+ roads[i][j][k].pop("fromj")
413
+ roads[i][j][k].pop("toi")
414
+ roads[i][j][k].pop("toj")
415
+ roads[i][j][k]["startIntersection"] = checkIntersection(roads[i][j][k]["startIntersection"], isTruelyInside)
416
+ roads[i][j][k]["endIntersection"] = checkIntersection(roads[i][j][k]["endIntersection"], isTruelyInside)
417
+ final_roads.append(roads[i][j][k])
418
+
419
+ return {
420
+ "intersections": final_intersecions,
421
+ "roads": final_roads
422
+ }
423
+
424
+ if __name__ == "__main__":
425
+ args = parser.parse_args()
426
+ path = '../data/roadnet/'
427
+ grid_config = json.load(open(path + args.config if args.config is not None else path + 'grid66.json'))
428
+ json.dump(gridToRoadnet(**grid_config), open(path + "roadnet66.json", "w"), indent=2)
third_party/CityFlow/tools/generator/readme.md CHANGED
@@ -1,41 +1,41 @@
1
- # Generator
2
-
3
- `generate_grid_scenario.py` can generate NxM grid road network with traffic flows.
4
-
5
- ### Quick Start
6
-
7
- To generate a 3x4 grid road network
8
-
9
- ```
10
- python generate_grid_scenario.py 3 4
11
- ```
12
-
13
- To generate a 3x4 grid road network with 2 straight lanes and with a predefined working traffic light plan
14
-
15
- ```
16
- python generate_grid_scenario.py 3 4 --numStraightLanes 2 --tlPlan
17
- ```
18
-
19
- ### Other arguments
20
- - `--rowDistance`: int, default=300, distance between consecutive intersections of each row (East-West Roads
21
- - `--columnDistance`: int, default=300, distance between consecutive intersections of each column (South-North Roads
22
- - `--intersectionWidth`: int, default=30
23
- - `--numLeftLanes`: int, default=1
24
- - `--numStraightLanes`: int, default=1
25
- - `--numRightLanes`: int, default=1
26
- - `--laneMaxSpeed`: int, default=16.67, meters/second
27
- - `--vehLen`: float, default=5.0, meters
28
- - `--vehWidth`: float, default=2.0, meters
29
- - `--vehMaxPosAcc`: float, default=2.0
30
- - `--vehMaxNegAcc`: float, default=4.5
31
- - `--vehUsualPosAcc`: float, default=2.0
32
- - `--vehUsualNegAcc`: float, default=4.5
33
- - `--vehMinGap`: float, default=2.5
34
- - `--vehMaxSpeed`: float, default=16.67
35
- - `--vehHeadwayTime`: float, default=1.5
36
- - `--dir`: str, default="./"
37
- - `--roadnetFile`: str, generated road network file
38
- - `--turn`: if specified, generate turning flows instead of straight flows
39
- - `--tlPlan`: if specified, generate working predefined traffic signal plan instead of plans with default orders
40
- - `--interval`: float, default=2.0, time (seconds) between each vehicle for each flow
41
- - `--flowFile`: str, generated flow file
 
1
+ # Generator
2
+
3
+ `generate_grid_scenario.py` can generate NxM grid road network with traffic flows.
4
+
5
+ ### Quick Start
6
+
7
+ To generate a 3x4 grid road network
8
+
9
+ ```
10
+ python generate_grid_scenario.py 3 4
11
+ ```
12
+
13
+ To generate a 3x4 grid road network with 2 straight lanes and with a predefined working traffic light plan
14
+
15
+ ```
16
+ python generate_grid_scenario.py 3 4 --numStraightLanes 2 --tlPlan
17
+ ```
18
+
19
+ ### Other arguments
20
+ - `--rowDistance`: int, default=300, distance between consecutive intersections of each row (East-West Roads
21
+ - `--columnDistance`: int, default=300, distance between consecutive intersections of each column (South-North Roads
22
+ - `--intersectionWidth`: int, default=30
23
+ - `--numLeftLanes`: int, default=1
24
+ - `--numStraightLanes`: int, default=1
25
+ - `--numRightLanes`: int, default=1
26
+ - `--laneMaxSpeed`: int, default=16.67, meters/second
27
+ - `--vehLen`: float, default=5.0, meters
28
+ - `--vehWidth`: float, default=2.0, meters
29
+ - `--vehMaxPosAcc`: float, default=2.0
30
+ - `--vehMaxNegAcc`: float, default=4.5
31
+ - `--vehUsualPosAcc`: float, default=2.0
32
+ - `--vehUsualNegAcc`: float, default=4.5
33
+ - `--vehMinGap`: float, default=2.5
34
+ - `--vehMaxSpeed`: float, default=16.67
35
+ - `--vehHeadwayTime`: float, default=1.5
36
+ - `--dir`: str, default="./"
37
+ - `--roadnetFile`: str, generated road network file
38
+ - `--turn`: if specified, generate turning flows instead of straight flows
39
+ - `--tlPlan`: if specified, generate working predefined traffic signal plan instead of plans with default orders
40
+ - `--interval`: float, default=2.0, time (seconds) between each vehicle for each flow
41
+ - `--flowFile`: str, generated flow file
training/README.md CHANGED
@@ -1,38 +1,38 @@
1
- # training
2
-
3
- Training, evaluation, device selection, and dataset utilities for the local multi-agent DQN stack.
4
-
5
- ## Main files
6
-
7
- - [train_local_policy.py](/Users/aditya/Developer/traffic-llm/training/train_local_policy.py)
8
- Main CLI for split generation, DQN training, and checkpoint evaluation.
9
- - [trainer.py](/Users/aditya/Developer/traffic-llm/training/trainer.py)
10
- Replay buffer, DQN training loop, checkpointing, validation, and aggregate metrics.
11
- - [rollout.py](/Users/aditya/Developer/traffic-llm/training/rollout.py)
12
- Evaluation helper shared by learned agents and rule-based baselines.
13
- - [models.py](/Users/aditya/Developer/traffic-llm/training/models.py)
14
- Dueling Q-network and running observation normalization.
15
- - [device.py](/Users/aditya/Developer/traffic-llm/training/device.py)
16
- Torch device selection for `cuda`, `mps`, or `cpu`.
17
- - [cityflow_dataset.py](/root/aditya/agentic-traffic/training/cityflow_dataset.py)
18
- City discovery, split loading, and scenario sampling.
19
-
20
- ## Main entry points
21
-
22
- - `python3 -m training.train_local_policy make-splits`
23
- - `python3 -m training.train_local_policy train`
24
- - `python3 -m training.train_local_policy train --max-val-cities 3 --val-scenarios-per-city 7`
25
- - `python3 -m training.train_local_policy train --max-train-cities 70 --max-val-cities 3 --val-scenarios-per-city 7 --policy-arch single_head_with_district_feature --reward-variant wait_queue_throughput`
26
- - `python3 -m training.train_local_policy train --policy-arch single_head_with_district_feature --reward-variant wait_queue_throughput`
27
- - `python3 -m training.train_local_policy evaluate --checkpoint artifacts/dqn_shared/best_validation.pt --split val`
28
- - `python3 -m training.train_local_policy evaluate --baseline queue_greedy --split val`
29
- - `tensorboard --logdir artifacts/dqn_shared/tensorboard`
30
-
31
- ## Training flow
32
-
33
- 1. Load city-level splits from `data/splits/`.
34
- 2. Sample one `(city, scenario)` episode at a time from the train split.
35
- 3. Run one shared Q-network across all controlled intersections in that city.
36
- 4. Collect per-intersection transitions into prioritized replay with n-step returns, using parallel CPU rollout workers by default.
37
- 5. Perform Double DQN updates against a target network.
38
- 6. Periodically evaluate on validation cities and save checkpoints. By default, eval and checkpoint cadence are both 40 updates, and each validation pass also writes an `update_XXXX.pt` checkpoint.
 
1
+ # training
2
+
3
+ Training, evaluation, device selection, and dataset utilities for the local multi-agent DQN stack.
4
+
5
+ ## Main files
6
+
7
+ - [train_local_policy.py](/Users/aditya/Developer/traffic-llm/training/train_local_policy.py)
8
+ Main CLI for split generation, DQN training, and checkpoint evaluation.
9
+ - [trainer.py](/Users/aditya/Developer/traffic-llm/training/trainer.py)
10
+ Replay buffer, DQN training loop, checkpointing, validation, and aggregate metrics.
11
+ - [rollout.py](/Users/aditya/Developer/traffic-llm/training/rollout.py)
12
+ Evaluation helper shared by learned agents and rule-based baselines.
13
+ - [models.py](/Users/aditya/Developer/traffic-llm/training/models.py)
14
+ Dueling Q-network and running observation normalization.
15
+ - [device.py](/Users/aditya/Developer/traffic-llm/training/device.py)
16
+ Torch device selection for `cuda`, `mps`, or `cpu`.
17
+ - [cityflow_dataset.py](/root/aditya/agentic-traffic/training/cityflow_dataset.py)
18
+ City discovery, split loading, and scenario sampling.
19
+
20
+ ## Main entry points
21
+
22
+ - `python3 -m training.train_local_policy make-splits`
23
+ - `python3 -m training.train_local_policy train`
24
+ - `python3 -m training.train_local_policy train --max-val-cities 3 --val-scenarios-per-city 7`
25
+ - `python3 -m training.train_local_policy train --max-train-cities 70 --max-val-cities 3 --val-scenarios-per-city 7 --policy-arch single_head_with_district_feature --reward-variant wait_queue_throughput`
26
+ - `python3 -m training.train_local_policy train --policy-arch single_head_with_district_feature --reward-variant wait_queue_throughput`
27
+ - `python3 -m training.train_local_policy evaluate --checkpoint artifacts/dqn_shared/best_validation.pt --split val`
28
+ - `python3 -m training.train_local_policy evaluate --baseline queue_greedy --split val`
29
+ - `tensorboard --logdir artifacts/dqn_shared/tensorboard`
30
+
31
+ ## Training flow
32
+
33
+ 1. Load city-level splits from `data/splits/`.
34
+ 2. Sample one `(city, scenario)` episode at a time from the train split.
35
+ 3. Run one shared Q-network across all controlled intersections in that city.
36
+ 4. Collect per-intersection transitions into prioritized replay with n-step returns, using parallel CPU rollout workers by default.
37
+ 5. Perform Double DQN updates against a target network.
38
+ 6. Periodically evaluate on validation cities and save checkpoints. By default, eval and checkpoint cadence are both 40 updates, and each validation pass also writes an `update_XXXX.pt` checkpoint.
training/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
- from training.cityflow_dataset import CityFlowDataset, ScenarioSpec
2
-
3
- __all__ = [
4
- "CityFlowDataset",
5
- "ScenarioSpec",
6
- ]
 
1
+ from training.cityflow_dataset import CityFlowDataset, ScenarioSpec
2
+
3
+ __all__ = [
4
+ "CityFlowDataset",
5
+ "ScenarioSpec",
6
+ ]
training/cityflow_dataset.py CHANGED
@@ -1,202 +1,202 @@
1
- from __future__ import annotations
2
-
3
- import random
4
- from dataclasses import dataclass
5
- from pathlib import Path
6
-
7
-
8
- DEFAULT_SCENARIOS = (
9
- "normal",
10
- "morning_rush",
11
- "evening_rush",
12
- "accident",
13
- "construction",
14
- "district_overload",
15
- "event_spike",
16
- )
17
-
18
-
19
- @dataclass(frozen=True)
20
- class ScenarioSpec:
21
- city_id: str
22
- scenario_name: str
23
- city_dir: Path
24
- scenario_dir: Path
25
- config_path: Path
26
- roadnet_path: Path
27
- district_map_path: Path
28
- metadata_path: Path
29
-
30
-
31
- class CityFlowDataset:
32
- def __init__(
33
- self,
34
- generated_root: str | Path = "data/generated",
35
- splits_root: str | Path = "data/splits",
36
- ):
37
- self.generated_root = Path(generated_root)
38
- self.splits_root = Path(splits_root)
39
-
40
- def discover_cities(self) -> list[str]:
41
- return sorted(
42
- city_dir.name
43
- for city_dir in self.generated_root.glob("city_*")
44
- if city_dir.is_dir() and (city_dir / "roadnet.json").exists()
45
- )
46
-
47
- def scenarios_for_city(self, city_id: str) -> list[str]:
48
- scenario_root = self.generated_root / city_id / "scenarios"
49
- return sorted(
50
- scenario_dir.name
51
- for scenario_dir in scenario_root.iterdir()
52
- if scenario_dir.is_dir()
53
- and (scenario_dir / "config.json").exists()
54
- and (scenario_dir / "flow.json").exists()
55
- )
56
-
57
- def load_split(self, split_name: str, create_if_missing: bool = True) -> list[str]:
58
- split_path = self.splits_root / f"{split_name}_cities.txt"
59
- if not split_path.exists():
60
- if not create_if_missing:
61
- raise FileNotFoundError(f"Missing split file: {split_path}")
62
- self.generate_default_splits()
63
-
64
- split_cities = [
65
- line.strip()
66
- for line in split_path.read_text().splitlines()
67
- if line.strip()
68
- ]
69
- available_cities = set(self.discover_cities())
70
- filtered_cities = [city_id for city_id in split_cities if city_id in available_cities]
71
- if filtered_cities:
72
- return filtered_cities
73
-
74
- if create_if_missing and available_cities:
75
- splits = self.generate_default_splits(overwrite=True)
76
- return splits.get(split_name, [])
77
-
78
- return filtered_cities
79
-
80
- def generate_default_splits(
81
- self,
82
- seed: int = 7,
83
- train_ratio: float = 0.7,
84
- val_ratio: float = 0.15,
85
- overwrite: bool = False,
86
- ) -> dict[str, list[str]]:
87
- self.splits_root.mkdir(parents=True, exist_ok=True)
88
-
89
- train_path = self.splits_root / "train_cities.txt"
90
- val_path = self.splits_root / "val_cities.txt"
91
- test_path = self.splits_root / "test_cities.txt"
92
- if not overwrite and train_path.exists() and val_path.exists() and test_path.exists():
93
- return {
94
- "train": self.load_split("train", create_if_missing=False),
95
- "val": self.load_split("val", create_if_missing=False),
96
- "test": self.load_split("test", create_if_missing=False),
97
- }
98
-
99
- city_ids = self.discover_cities()
100
- rng = random.Random(seed)
101
- rng.shuffle(city_ids)
102
-
103
- num_cities = len(city_ids)
104
- if num_cities == 0:
105
- splits = {"train": [], "val": [], "test": []}
106
- elif num_cities == 1:
107
- splits = {"train": city_ids[:], "val": city_ids[:], "test": city_ids[:]}
108
- elif num_cities == 2:
109
- splits = {
110
- "train": sorted(city_ids[:1]),
111
- "val": sorted(city_ids[1:2]),
112
- "test": sorted(city_ids[:1]),
113
- }
114
- else:
115
- train_count = max(1, int(num_cities * train_ratio))
116
- val_count = int(num_cities * val_ratio)
117
- if train_count + val_count >= num_cities:
118
- val_count = max(0, num_cities - train_count - 1)
119
- test_count = num_cities - train_count - val_count
120
- if test_count <= 0:
121
- test_count = 1
122
- if val_count > 0:
123
- val_count -= 1
124
- else:
125
- train_count = max(1, train_count - 1)
126
-
127
- splits = {
128
- "train": sorted(city_ids[:train_count]),
129
- "val": sorted(city_ids[train_count : train_count + val_count]),
130
- "test": sorted(city_ids[train_count + val_count :]),
131
- }
132
-
133
- for split_name, city_list in splits.items():
134
- split_path = self.splits_root / f"{split_name}_cities.txt"
135
- split_path.write_text("\n".join(city_list) + "\n")
136
-
137
- return splits
138
-
139
- def build_scenario_spec(self, city_id: str, scenario_name: str) -> ScenarioSpec:
140
- city_dir = self.generated_root / city_id
141
- scenario_dir = city_dir / "scenarios" / scenario_name
142
- return ScenarioSpec(
143
- city_id=city_id,
144
- scenario_name=scenario_name,
145
- city_dir=city_dir,
146
- scenario_dir=scenario_dir,
147
- config_path=scenario_dir / "config.json",
148
- roadnet_path=city_dir / "roadnet.json",
149
- district_map_path=city_dir / "district_map.json",
150
- metadata_path=city_dir / "metadata.json",
151
- )
152
-
153
- def sample_scenario(
154
- self,
155
- split_name: str,
156
- rng: random.Random,
157
- city_id: str | None = None,
158
- scenario_name: str | None = None,
159
- ) -> ScenarioSpec:
160
- available_cities = self.load_split(split_name)
161
- if not available_cities:
162
- available_cities = self.discover_cities()
163
- if not available_cities:
164
- raise FileNotFoundError(
165
- f"No generated cities found under {self.generated_root} for split '{split_name}'."
166
- )
167
- selected_city = city_id or rng.choice(available_cities)
168
- selected_scenario = scenario_name or rng.choice(self.scenarios_for_city(selected_city))
169
- return self.build_scenario_spec(selected_city, selected_scenario)
170
-
171
- def iter_scenarios(
172
- self,
173
- split_name: str,
174
- scenarios_per_city: int | None = None,
175
- max_cities: int | None = None,
176
- diversify_single_scenario: bool = False,
177
- ) -> list[ScenarioSpec]:
178
- scenario_specs: list[ScenarioSpec] = []
179
- city_ids = self.load_split(split_name)
180
- if max_cities is not None:
181
- city_ids = city_ids[:max_cities]
182
- for city_index, city_id in enumerate(city_ids):
183
- scenario_names = self.scenarios_for_city(city_id)
184
- if (
185
- diversify_single_scenario
186
- and scenarios_per_city == 1
187
- and scenario_names
188
- ):
189
- preferred_order = [
190
- scenario_name
191
- for scenario_name in DEFAULT_SCENARIOS
192
- if scenario_name in scenario_names
193
- ]
194
- if preferred_order:
195
- scenario_names = [preferred_order[city_index % len(preferred_order)]]
196
- else:
197
- scenario_names = [scenario_names[0]]
198
- elif scenarios_per_city is not None:
199
- scenario_names = scenario_names[:scenarios_per_city]
200
- for scenario_name in scenario_names:
201
- scenario_specs.append(self.build_scenario_spec(city_id, scenario_name))
202
- return scenario_specs
 
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+
8
+ DEFAULT_SCENARIOS = (
9
+ "normal",
10
+ "morning_rush",
11
+ "evening_rush",
12
+ "accident",
13
+ "construction",
14
+ "district_overload",
15
+ "event_spike",
16
+ )
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class ScenarioSpec:
21
+ city_id: str
22
+ scenario_name: str
23
+ city_dir: Path
24
+ scenario_dir: Path
25
+ config_path: Path
26
+ roadnet_path: Path
27
+ district_map_path: Path
28
+ metadata_path: Path
29
+
30
+
31
+ class CityFlowDataset:
32
+ def __init__(
33
+ self,
34
+ generated_root: str | Path = "data/generated",
35
+ splits_root: str | Path = "data/splits",
36
+ ):
37
+ self.generated_root = Path(generated_root)
38
+ self.splits_root = Path(splits_root)
39
+
40
+ def discover_cities(self) -> list[str]:
41
+ return sorted(
42
+ city_dir.name
43
+ for city_dir in self.generated_root.glob("city_*")
44
+ if city_dir.is_dir() and (city_dir / "roadnet.json").exists()
45
+ )
46
+
47
+ def scenarios_for_city(self, city_id: str) -> list[str]:
48
+ scenario_root = self.generated_root / city_id / "scenarios"
49
+ return sorted(
50
+ scenario_dir.name
51
+ for scenario_dir in scenario_root.iterdir()
52
+ if scenario_dir.is_dir()
53
+ and (scenario_dir / "config.json").exists()
54
+ and (scenario_dir / "flow.json").exists()
55
+ )
56
+
57
+ def load_split(self, split_name: str, create_if_missing: bool = True) -> list[str]:
58
+ split_path = self.splits_root / f"{split_name}_cities.txt"
59
+ if not split_path.exists():
60
+ if not create_if_missing:
61
+ raise FileNotFoundError(f"Missing split file: {split_path}")
62
+ self.generate_default_splits()
63
+
64
+ split_cities = [
65
+ line.strip()
66
+ for line in split_path.read_text().splitlines()
67
+ if line.strip()
68
+ ]
69
+ available_cities = set(self.discover_cities())
70
+ filtered_cities = [city_id for city_id in split_cities if city_id in available_cities]
71
+ if filtered_cities:
72
+ return filtered_cities
73
+
74
+ if create_if_missing and available_cities:
75
+ splits = self.generate_default_splits(overwrite=True)
76
+ return splits.get(split_name, [])
77
+
78
+ return filtered_cities
79
+
80
+ def generate_default_splits(
81
+ self,
82
+ seed: int = 7,
83
+ train_ratio: float = 0.7,
84
+ val_ratio: float = 0.15,
85
+ overwrite: bool = False,
86
+ ) -> dict[str, list[str]]:
87
+ self.splits_root.mkdir(parents=True, exist_ok=True)
88
+
89
+ train_path = self.splits_root / "train_cities.txt"
90
+ val_path = self.splits_root / "val_cities.txt"
91
+ test_path = self.splits_root / "test_cities.txt"
92
+ if not overwrite and train_path.exists() and val_path.exists() and test_path.exists():
93
+ return {
94
+ "train": self.load_split("train", create_if_missing=False),
95
+ "val": self.load_split("val", create_if_missing=False),
96
+ "test": self.load_split("test", create_if_missing=False),
97
+ }
98
+
99
+ city_ids = self.discover_cities()
100
+ rng = random.Random(seed)
101
+ rng.shuffle(city_ids)
102
+
103
+ num_cities = len(city_ids)
104
+ if num_cities == 0:
105
+ splits = {"train": [], "val": [], "test": []}
106
+ elif num_cities == 1:
107
+ splits = {"train": city_ids[:], "val": city_ids[:], "test": city_ids[:]}
108
+ elif num_cities == 2:
109
+ splits = {
110
+ "train": sorted(city_ids[:1]),
111
+ "val": sorted(city_ids[1:2]),
112
+ "test": sorted(city_ids[:1]),
113
+ }
114
+ else:
115
+ train_count = max(1, int(num_cities * train_ratio))
116
+ val_count = int(num_cities * val_ratio)
117
+ if train_count + val_count >= num_cities:
118
+ val_count = max(0, num_cities - train_count - 1)
119
+ test_count = num_cities - train_count - val_count
120
+ if test_count <= 0:
121
+ test_count = 1
122
+ if val_count > 0:
123
+ val_count -= 1
124
+ else:
125
+ train_count = max(1, train_count - 1)
126
+
127
+ splits = {
128
+ "train": sorted(city_ids[:train_count]),
129
+ "val": sorted(city_ids[train_count : train_count + val_count]),
130
+ "test": sorted(city_ids[train_count + val_count :]),
131
+ }
132
+
133
+ for split_name, city_list in splits.items():
134
+ split_path = self.splits_root / f"{split_name}_cities.txt"
135
+ split_path.write_text("\n".join(city_list) + "\n")
136
+
137
+ return splits
138
+
139
+ def build_scenario_spec(self, city_id: str, scenario_name: str) -> ScenarioSpec:
140
+ city_dir = self.generated_root / city_id
141
+ scenario_dir = city_dir / "scenarios" / scenario_name
142
+ return ScenarioSpec(
143
+ city_id=city_id,
144
+ scenario_name=scenario_name,
145
+ city_dir=city_dir,
146
+ scenario_dir=scenario_dir,
147
+ config_path=scenario_dir / "config.json",
148
+ roadnet_path=city_dir / "roadnet.json",
149
+ district_map_path=city_dir / "district_map.json",
150
+ metadata_path=city_dir / "metadata.json",
151
+ )
152
+
153
+ def sample_scenario(
154
+ self,
155
+ split_name: str,
156
+ rng: random.Random,
157
+ city_id: str | None = None,
158
+ scenario_name: str | None = None,
159
+ ) -> ScenarioSpec:
160
+ available_cities = self.load_split(split_name)
161
+ if not available_cities:
162
+ available_cities = self.discover_cities()
163
+ if not available_cities:
164
+ raise FileNotFoundError(
165
+ f"No generated cities found under {self.generated_root} for split '{split_name}'."
166
+ )
167
+ selected_city = city_id or rng.choice(available_cities)
168
+ selected_scenario = scenario_name or rng.choice(self.scenarios_for_city(selected_city))
169
+ return self.build_scenario_spec(selected_city, selected_scenario)
170
+
171
+ def iter_scenarios(
172
+ self,
173
+ split_name: str,
174
+ scenarios_per_city: int | None = None,
175
+ max_cities: int | None = None,
176
+ diversify_single_scenario: bool = False,
177
+ ) -> list[ScenarioSpec]:
178
+ scenario_specs: list[ScenarioSpec] = []
179
+ city_ids = self.load_split(split_name)
180
+ if max_cities is not None:
181
+ city_ids = city_ids[:max_cities]
182
+ for city_index, city_id in enumerate(city_ids):
183
+ scenario_names = self.scenarios_for_city(city_id)
184
+ if (
185
+ diversify_single_scenario
186
+ and scenarios_per_city == 1
187
+ and scenario_names
188
+ ):
189
+ preferred_order = [
190
+ scenario_name
191
+ for scenario_name in DEFAULT_SCENARIOS
192
+ if scenario_name in scenario_names
193
+ ]
194
+ if preferred_order:
195
+ scenario_names = [preferred_order[city_index % len(preferred_order)]]
196
+ else:
197
+ scenario_names = [scenario_names[0]]
198
+ elif scenarios_per_city is not None:
199
+ scenario_names = scenario_names[:scenarios_per_city]
200
+ for scenario_name in scenario_names:
201
+ scenario_specs.append(self.build_scenario_spec(city_id, scenario_name))
202
+ return scenario_specs