Traffic Simulator is a full-stack academic project that models vehicle behavior at a two-street signalized intersection. A Julia backend runs a continuous-space agent-based simulation, a Genie API exposes independently addressable simulation instances, and a React interface visualizes vehicles, traffic lights, controls, and velocity metrics.
The project explores how local rules - vehicle following, traffic-light response, acceleration, braking, and periodic road boundaries - produce observable traffic patterns at the system level.
Note
This is a public educational project. The model is intended for experimentation and software-learning purposes; it is not a calibrated transportation model, a road-safety tool, or a traffic-engineering decision system.
The simulator represents a simplified intersection formed by two perpendicular avenues:
- West Avenue (
av1) carries vehicles horizontally along the positive X direction. - North Avenue (
av2) carries vehicles vertically toward decreasing Y coordinates. - Each avenue contains two fixed lanes selected randomly when vehicles are created.
- One traffic light controls each avenue.
- Signal phases are offset so one avenue begins with a permissive phase while the other begins with a restrictive phase.
- Vehicles detect the closest car and traffic light ahead, choose a target velocity, smooth the transition, and move through a periodic continuous space.
- The browser advances the backend one step per polling request and renders the returned state as SVG elements.
- Plotly records average speed by avenue and the combined speed sum for later comparison.
The implementation was inspired by Uri Wilensky's NetLogo Traffic Intersection model and adapted into a distributed Julia-and-React architecture.
The interface provides:
- Setup, start, and stop controls.
- A simulation-speed slider.
- Independent vehicle-count controls for North Avenue and West Avenue.
- An SVG intersection with live vehicle and signal positions.
- A Plotly chart generated when the run is stopped.
This project was developed collaboratively as a university exercise in computational modeling and multi-agent systems.
The main learning goals were to:
- Represent traffic participants as autonomous agents.
- Model a signalized intersection with coordinated phase timing.
- Apply local perception rules to vehicle movement.
- Separate simulation logic from visualization.
- Expose simulation state through an HTTP API.
- Connect Julia and JavaScript in a full-stack workflow.
- Visualize position and velocity data in the browser.
- Compare aggregate behavior under different vehicle densities and update rates.
Important
The original project notes that development was performed collaboratively and synchronously. Commit counts should not be interpreted as a complete measure of each contributor's work.
| Capability | Implementation | Status |
|---|---|---|
| Continuous Traffic Model | Agents.jl ContinuousSpace with two-dimensional positions and velocities |
Implemented |
| Vehicle Agents | Street assignment, orientation, perception, target-velocity selection, smoothing, and movement | Implemented |
| Traffic-Light Agents | Green, yellow, and red states driven by a synchronized counter | Implemented |
| Two-Lane Streets | Two fixed coordinate lanes per avenue with randomized initial placement | Implemented |
| Periodic Traffic Flow | Vehicles wrap across the continuous-space boundaries | Implemented |
| Simulation Instances | UUID-addressed models stored independently in the API process | Implemented in memory |
| Interactive Controls | Setup, start, stop, update speed, and vehicle-count sliders | Implemented |
| SVG Visualization | Live intersection, vehicle sprites, and state-dependent signal sprites | Implemented |
| Velocity Analytics | Per-avenue averages and a combined velocity sum plotted with Plotly | Implemented |
| Automated Tests | Unit, API, and browser tests | Not included |
The project separates the numerical model, HTTP transport, and visual client.
flowchart TD
U["User Controls"] --> R["React and Vite Client"]
R -->|"POST setup"| G["Genie API"]
R -->|"GET next step"| G
G --> J["Agents.jl Model"]
J --> G
G -->|"JSON agents"| R
R --> V["SVG Scene and Plotly Chart"]
| Component | Responsibility |
|---|---|
simple.jl |
Defines agent types, perception rules, movement logic, signal timing, initial positions, and the simulation model. |
webapi.jl |
Creates simulation instances, advances models, serializes agent state, configures CORS, and starts Genie. |
fe/src/App.jsx |
Manages controls, API polling, React state, SVG rendering, speed sampling, and Plotly output. |
fe/public/ |
Stores vehicle and traffic-light sprites used by the SVG scene. |
image.png |
Provides a repository-level screenshot of the running interface. |
| Area | Technologies |
|---|---|
| Simulation Language | Julia |
| Agent-Based Modeling | Agents.jl |
| Vector Representation | StaticArrays and SVector |
| Randomization | Julia Random and MersenneTwister |
| Backend Framework | Genie |
| HTTP and Serialization | Genie JSON renderer, Genie Requests, HTTP.jl |
| Instance Identification | UUIDs |
| Frontend | React 18, JavaScript, JSX |
| Development Server and Build | Vite 5 |
| Interface Components | AWS Amplify UI React |
| Visualization | SVG and image sprites |
| Analytics | Plotly.js |
| Code Quality | ESLint with React and Hooks plugins |
Note
JavaScript dependencies are locked through package-lock.json. The repository does not currently include Julia Project.toml or Manifest.toml files, so Julia package versions are not pinned.
Traffic-Simulator-main/
├── README.md # Project documentation
├── LICENSE # MIT license
├── image.png # Interface screenshot
├── simple.jl # Agent model and simulation rules
├── webapi.jl # Genie HTTP API
├── Reflexiones/ # Academic reflection documents
└── fe/
├── package.json # Frontend scripts and dependencies
├── package-lock.json # Locked npm dependency graph
├── vite.config.js # Vite React configuration
├── public/ # Car and traffic-light image assets
└── src/
├── App.jsx # Controls, polling, SVG, and analytics
├── App.css # Component-level styles
├── index.css # Global Vite template styles
└── main.jsx # React application entry point
The backend creates a two-dimensional continuous environment with the following configuration:
| Parameter | Value | Meaning |
|---|---|---|
| Extent | (28, 15) |
Width and height of the simulation coordinate system. |
| Spacing | 0.5 |
Spatial indexing resolution used by Agents.jl. |
| Periodic | true |
Agents leaving one boundary re-enter from the opposite side. |
| Scheduler | Schedulers.fastest |
Uses Agents.jl's low-overhead scheduler. |
| Agent Union | Car and stopLight |
Both types coexist in the same model. |
| Movement interval | 0.4 |
Value passed to move_agent! for each vehicle step. |
Periodic boundaries create a closed traffic loop. Vehicles are not deleted after crossing the visible road; they wrap around and continue circulating.
Both agent types extend ContinuousAgent{2, Float64}, which provides an identifier, position, and velocity.
| Field | Type | Purpose |
|---|---|---|
id |
Inherited | Unique Agents.jl identifier. |
pos |
Inherited | Continuous (x, y) location. |
vel |
Inherited | Two-dimensional velocity vector. |
accelerating |
Bool |
Reserved state flag; initialized but not currently used by the controller. |
street |
Streets |
Associates the vehicle with av1 or av2. |
orientation |
Float64 |
Determines the movement direction through trigonometric projection. |
| Field | Type | Purpose |
|---|---|---|
id |
Inherited | Unique Agents.jl identifier. |
pos |
Inherited | Fixed location near the intersection. |
vel |
Inherited | Zero vector; traffic lights do not move. |
status |
LightColor |
Current green, yellow, or red state. |
time_counter |
Int |
Current tick inside the signal cycle. |
street |
Streets |
Avenue controlled by the signal. |
@enum LightColor green yellow red
@enum Streets av1 av2Enumerations keep the simulation state constrained to known values and are serialized as readable values for the frontend.
| Avenue | Model Name | Direction | Fixed Lane Coordinates | Random Spawn Range | Initial Velocity |
|---|---|---|---|---|---|
| West Avenue | av1 |
Positive X | y = 7 or y = 8 |
x = 5.0...20.0 in 0.5 increments |
(0.1, 0.0) |
| North Avenue | av2 |
Decreasing Y | x = 13 or x = 14 |
y = 0.0...10.0 in 0.5 increments |
(0.0, 0.1) |
The lane is selected randomly when each vehicle is created. Vehicles remain in their assigned lane; the current implementation does not perform lane changes.
| Controlled Avenue | Position | Initial State | Initial Counter |
|---|---|---|---|
North Avenue (av2) |
(12.0, 3.5) |
Green | 0 |
West Avenue (av1) |
(16.3, 8.5) |
Red | 60 |
The 60-tick counter offset places the two lights in opposite halves of the same 120-tick cycle.
The signal constants are:
green_duration = 45
yellow_duration = 15
cycle_length = 2 * (green_duration + yellow_duration) # 120 ticksEach traffic-light agent increments its counter once per simulation step.
stateDiagram-v2
[*] --> Green
Green --> Yellow: After 45 ticks
Yellow --> Red: After 15 ticks
Red --> Green: After 60 ticks
| Counter Range | State | Duration |
|---|---|---|
1...45 |
Green | 45 ticks |
46...60 |
Yellow | 15 ticks |
61...120 |
Red | 60 ticks |
When the counter exceeds 120, it resets to 1. Because the second light begins at 60, its red interval aligns with the first light's green-and-yellow interval.
Note
Signal time is measured in model ticks, not real-world seconds. Wall-clock duration depends on the browser polling rate and request latency.
Each car performs two local searches before selecting its next velocity.
closest_car_ahead filters nearby agents using four conditions:
- The neighboring agent must also be a
Car. - Both cars must belong to the same avenue.
- Both cars must occupy the same fixed lane coordinate.
- The neighbor must be ahead according to the avenue's travel direction.
| Avenue | Search Radius | Same-Lane Test | Ahead Test | Distance |
|---|---|---|---|---|
av1 |
20.5 |
Equal Y | Neighbor X is greater | neighbor.x - car.x |
av2 |
1.8 |
Equal X | Neighbor Y is smaller | car.y - neighbor.y |
The function returns both the closest matching vehicle and its distance. If no vehicle qualifies, it returns nothing and Inf.
closest_light_ahead searches within a radius of 20.0 and selects a stopLight associated with the same avenue.
The horizontal avenue uses direct X-distance. The vertical avenue applies a custom coordinate adjustment so the continuous model aligns with the rendered stop line.
Important
Perception is intentionally local and rule-based. The model does not include sensors, occlusion, driver reaction-time distributions, vehicle length, or probabilistic behavior.
Every car executes the same priority-based controller.
flowchart TD
P["Perceive nearest car and light"] --> C{"Car ahead is closer?"}
C -->|"Yes"| F["Follow: accelerate, stop, or reverse"]
C -->|"No"| L{"Red or yellow light ahead?"}
L -->|"Yes"| B["Brake or reverse near stop line"]
L -->|"No"| A["Accelerate"]
F --> S["Smooth velocity and move"]
B --> S
A --> S
If a car is ahead and closer than the nearest traffic light, distance determines the target behavior.
| Ordered Condition | Behavior |
|---|---|
1.2 <= distance <= 2.5 |
Select the stop target velocity. |
Otherwise, distance below 2.65 on av1 or 2.4 on av2 |
Select the reverse target velocity. |
| Any larger distance | Select the acceleration target velocity. |
Conditions are evaluated in order. The controller uses reverse movement as a corrective response when agents become too close or cross a configured threshold.
If no closer vehicle controls the decision, the car evaluates the nearest signal.
| Signal and Distance | Behavior |
|---|---|
| Green | Accelerate. |
| Red or yellow, within the braking interval | Select the stop target velocity. |
Red or yellow, closer than 1.8 adjusted units |
Select the reverse target velocity. |
| No relevant signal | Accelerate. |
The braking interval extends to 3.5 units on av1 and 8.5 adjusted units on av2.
The simulation uses a heuristic kinematic controller rather than a calibrated physical model.
For each avenue, the backend calculates three candidate velocities:
accelerate: increases motion in the avenue's forward direction.stop: reduces the current speed toward zero.reverse: introduces a limited corrective movement when an agent is too close to an obstacle or stop line.
After selecting a target, the current velocity is blended with it using linear interpolation:
v_next = v_current * (1 - alpha) + v_target * alpha
alpha = 0.18
Therefore:
v_next = 0.82 * v_current + 0.18 * v_target
This exponential-style smoothing prevents an instantaneous jump from the current velocity to the newly selected target.
Direction is projected with trigonometric terms:
- Horizontal motion uses
cos(orientation). - Vertical motion uses
sin(orientation). av1starts with orientation0.av2starts with orientation3pi/2, producing motion toward decreasing Y.
Caution
Acceleration, braking, and reverse formulas were tuned for visual behavior in this simplified coordinate system. Values are not expressed in SI units and should not be interpreted as measured vehicle dynamics.
The Genie service runs on http://localhost:8000 by default.
POST /simulations
Content-Type: application/jsonRequest body:
{
"numCarsN": 3,
"numCarsO": 2
}Response structure:
{
"Location": "/simulations/<uuid>",
"cars": [
{
"id": 3,
"pos": [12.5, 7.0],
"vel": [0.1, 0.0],
"street": "av1",
"orientation": 0.0
}
],
"stopLights": [
{
"id": 1,
"pos": [12.0, 3.5],
"status": "green",
"time_counter": 0,
"street": "av2"
}
]
}The exact serialized objects may include additional agent fields.
GET /simulations/:idThis endpoint:
- Retrieves the model from the in-memory
instancesdictionary. - Executes
run!(model, 1). - Separates cars and traffic lights.
- Returns the updated agent state.
Response structure:
{
"cars": [],
"stopLights": []
}Important
GET /simulations/:id is not a read-only operation: every request advances the model by one tick. Multiple clients polling the same identifier will advance the same model more quickly and may observe interleaved states.
curl -X POST http://localhost:8000/simulations \
-H "Content-Type: application/json" \
-d '{"numCarsN":3,"numCarsO":2}'Copy the returned Location and request the next state:
curl http://localhost:8000/simulations/<uuid>Each setup request generates a UUID and stores the corresponding Agents.jl model in a process-level dictionary:
instances[uuid] = model
This design allows several browser sessions to create separate simulations without sharing car positions or signal counters.
The implementation is intentionally lightweight:
- State exists only in server memory.
- Restarting Julia removes every simulation.
- Instances do not currently expire.
- There is no endpoint for deleting a simulation.
- The server does not persist run history.
- Concurrent access is not explicitly synchronized.
The React client follows three phases.
Setup sends the selected vehicle counts to POST /simulations and stores:
- The returned simulation location.
- Initial vehicle state.
- Initial traffic-light state.
Start clears previous measurements and creates a JavaScript interval. Each interval callback requests the next state and updates the SVG.
The timer delay is:
poll_interval_ms = 500 / simulation_speed
With a slider range from 1 to 30, the requested interval ranges from 500 ms to approximately 16.7 ms.
Ignoring network and processing delay, the nominal request frequency is:
requests_per_second = 1000 / poll_interval_ms
= 2 * simulation_speed
Because one request advances one tick, the browser acts as the simulation clock.
Caution
setInterval does not wait for the preceding fetch to complete. At high speed values, requests can overlap if the backend or network takes longer than the configured interval.
Stop clears the interval, re-enables setup controls, and builds the Plotly chart from the accumulated samples.
The browser does not reuse backend coordinates directly as pixels. It applies avenue-specific scaling to align agent positions with the illustrated intersection.
| Object | Browser X | Browser Y |
|---|---|---|
av1 car |
x * 35 |
50 + y * 20 |
av2 car |
x * 32 |
y * 34 |
| Traffic light | -13 + x * 32 |
83 + y * 20 |
The SVG map is composed from green terrain rectangles and gray road rectangles. Vehicle and traffic-light states are rendered with PNG sprites stored in fe/public/.
Traffic-light image selection is state-driven:
red -> SemRojo.png
yellow -> SemAmarillo.png
green -> SemVerde.png
For every returned car, the frontend calculates speed magnitude:
speed = sqrt(vx^2 + vy^2)
Samples are grouped by avenue.
| Series | Calculation | Chart Color |
|---|---|---|
| West Avenue | Sum of av1 speed magnitudes divided by numCarsO |
Light blue |
| North Avenue | Sum of av2 speed magnitudes divided by numCarsN |
Light red |
| Combined Speed | Sum of all speed magnitudes across both avenues | Black |
The first two series are per-avenue averages, while the black series is a total rather than an average. The horizontal axis represents polling samples, not guaranteed wall-clock seconds or simulation time units.
Warning
Selecting zero cars for an avenue causes its average calculation to divide by zero. A production-quality revision should return 0, null, or omit the empty series instead of producing NaN.
The repository does not declare exact Node.js or Julia engine versions. Vite 5 and the Agents.jl APIs used by the code must be supported by the installed runtimes.
Clone or download this repository, then enter its project directory:
cd Traffic-Simulator-mainFrom the project root, start Julia and install the required packages:
using Pkg
Pkg.add(["Agents", "StaticArrays", "Genie", "HTTP"])Tip
For reproducible development, create a Julia environment with Pkg.activate(".") before adding dependencies, then commit the generated Project.toml and Manifest.toml files.
From the repository root:
julia webapi.jlGenie should start the API on:
http://localhost:8000
Open a second terminal:
cd fe
npm installnpm run devOpen the local URL printed by Vite, normally:
http://localhost:5173
- Select the number of cars for each avenue.
- Select a simulation speed.
- Click Setup to create a backend model.
- Click Start to begin polling and rendering.
- Observe vehicle and traffic-light behavior.
- Click Stop to stop polling and generate the velocity chart.
Important
Start the backend before the frontend and click Setup before Start. The current interface does not provide complete recovery messages for a missing API connection or an uninitialized simulation location.
Run these commands from fe/:
| Command | Purpose |
|---|---|
npm run dev |
Starts the Vite development server with hot reload. |
npm run build |
Creates a production frontend bundle. |
npm run lint |
Runs ESLint over the frontend source. |
npm run preview |
Serves the generated production bundle locally. |
The original project documentation attributes the work to:
- Abigail Pérez García
- Rodrigo López Guerra
The documented contribution areas include:
| Area | Documented Work |
|---|---|
| Intersection and Controls | SVG map construction, initial interface controls, simulation-speed control, and vehicle-count configuration. |
| Vehicle Foundations | Initial vehicle-agent definition and movement-rule exploration. |
| Signal Logic | Traffic-light agents, phase counters, state changes, and opposite-phase initialization. |
| Vehicle Controller | Perception, distance rules, target velocities, smoothing, and movement integration. |
| Scenario Population | Vehicles for both avenues and randomized fixed-lane placement. |
| Analytics | Speed magnitude, per-avenue sampling, and Plotly chart generation. |
The project documentation emphasizes synchronous collaboration; the contribution table summarizes areas described by the team and is not intended as a commit-by-commit ownership record.
- Continuous instead of grid-only movement: vehicle positions and velocities can change by fractional values.
- Local agent perception: each car reacts only to nearby cars and signals rather than a centralized traffic controller.
- Periodic boundaries: vehicles recirculate, supporting long-running observation without spawn-and-despawn logic.
- Opposed signal phases: a single counter offset coordinates the two avenues with minimal state.
- Heuristic velocity smoothing: linear interpolation produces visually gradual movement changes.
- Client-driven stepping: the API remains simple while the frontend controls run speed through polling frequency.
- Instance-specific URLs: UUIDs keep separate setup requests logically isolated.
- SVG rendering: the intersection remains lightweight and directly driven by React state.
- Post-run plotting: measurements are accumulated during execution and visualized when the user stops the run.
The repository is a functional academic prototype with intentionally simplified behavior.
- No turning vehicles or turning signals.
- No pedestrians, crossings, priority vehicles, or traffic incidents.
- Fixed lanes without lane-changing behavior.
- Vehicles may spawn at overlapping or very close coordinates.
- No explicit vehicle length, maximum acceleration, or realistic braking distance.
- Heuristic reverse motion can create behavior that would not be permitted in real traffic.
- Signal timing is measured in request-driven ticks rather than physical seconds.
- Detection rules near periodic boundaries do not fully model wrapped forward distance.
- Random initialization is not exposed as a reproducible user seed.
- Simulation state is memory-only and disappears on restart.
- Instances do not expire and cannot be deleted through the API.
- Missing or unknown UUIDs do not receive a dedicated domain-level error response.
- Vehicle counts are not validated server-side.
- CORS accepts every origin.
- No authentication, rate limiting, persistence, or concurrency control.
- A state-changing operation is exposed through
GET.
- API URLs are hardcoded to
localhost:8000. - Fetch failures are not surfaced through a user-facing error state.
- Start can be requested without a valid setup response.
- A zero vehicle count can produce division-by-zero analytics.
- High simulation speeds can produce overlapping HTTP requests.
- The SVG uses fixed dimensions and is not fully responsive.
- Plot time is a sample index rather than normalized simulation time.
- No Julia environment manifest.
- No automated unit, integration, API, or browser tests.
- No continuous-integration workflow.
- No structured backend logging or performance instrumentation.
A future automated test suite should cover:
- Traffic-light transitions at ticks
45,46,60,61,120, and reset. - Opposite-phase behavior of the two lights.
- Closest-car selection for each avenue and lane.
- Closest-light selection in both coordinate systems.
- Acceleration when no obstacle exists.
- Stop and reverse thresholds.
- Velocity smoothing with known inputs.
- Periodic boundary wrapping.
- Deterministic initialization with a fixed random seed.
- Valid simulation creation.
- Rejection of negative, non-integer, or excessive vehicle counts.
- Isolation between two UUID instances.
- Correct advancement by exactly one step.
- Unknown and expired identifiers.
- Simulation deletion and lifecycle cleanup.
- Setup request payloads.
- Button state transitions.
- Polling start and cleanup.
- Zero-car analytics.
- Sprite selection by traffic-light state.
- Coordinate transformations by avenue.
- Plotly series calculations.
- Error messages when the API is unavailable.
Future versions could include:
- Julia
Project.tomlandManifest.tomlfiles for reproducible installation. - Environment-based API configuration.
- Schema validation for all request parameters.
- Explicit
POST /simulations/:id/stepssemantics for state changes. - Simulation metadata, deletion, expiration, and persistence.
- A server-owned simulation clock or WebSocket state stream.
- Protection against overlapping client requests.
- Seed selection for repeatable experiments.
- Non-overlapping vehicle placement.
- Correct empty-avenue analytics.
- Normalized time values returned by the backend.
- Live Plotly updates instead of post-run rendering only.
- Configurable green, yellow, and red durations.
- Realistic car-following models such as IDM.
- Calibrated acceleration, braking, reaction time, and vehicle length.
- Turning movements, lane changes, pedestrian phases, and collision detection.
- Queue length, waiting time, throughput, density, and travel-time metrics.
- Responsive SVG scaling and improved accessibility.
- Unit, integration, API, and end-to-end tests.
- Continuous integration for Julia and JavaScript.
The repository contains a complete academic prototype with:
- A runnable Agents.jl traffic model.
- Coordinated traffic-light agents.
- Rule-based vehicle behavior.
- A Genie API for isolated simulations.
- A React/Vite visualization client.
- Interactive scenario controls.
- Post-run Plotly speed analytics.
- Public source code under the MIT License.
No automated test suite, Julia environment lock, or production deployment configuration was identified in the reviewed repository.
The project was conceptually inspired by:
Wilensky, U. (1998). NetLogo Traffic Intersection model. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL.
View the NetLogo Traffic Intersection model
This project is licensed under the MIT License.
Copyright (c) 2026 Rodrigo López Guerra.
The software is provided without warranty, subject to the terms included in the repository license.

