Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

Bursa Air Quality & Runner Tracking — Real-Time System

A real-time air quality monitoring and runner participation tracking system for Bursa, Turkey. Built with Vert.x Event Bus, WebSocket, and multiple external API integrations.

Architecture

[Airqoon API]          [OpenWeatherMap API]
     |  GET /60s              |  GET /5min
     v                        v
DataFetcherVerticle    WeatherFetcherVerticle
     |                        |
     +--- EventBus -----------+
     |    publish/subscribe   |
     v         v              v
AlertVerticle  RunSessionVerticle  WebSocketVerticle
(PM2.5>100)    (join/leave)        (HTTP + 2x WebSocket)
                                        |
                    +-------------------+-------------------+
                    |                   |                   |
              index.html          runners.html          map.html
              (AQI Dashboard)     (Runner Tracker)      (Leaflet Map)

Communication: Vert.x EventBus (publish/subscribe + request/reply)
Transport:     WebSocket (server push to all connected browsers)

Verticle Responsibilities

Verticle Role
DataFetcherVerticle Polls 28 air quality sensors every 60s from Airqoon API, parses telemetry (PM2.5, PM10, NO2, SO2, temperature, humidity), publishes to EventBus
WeatherFetcherVerticle Fetches weather data from OpenWeatherMap for 12 preset locations every 5 min, caches with 2-min TTL, serves via request/reply
AlertVerticle Listens for PM2.5 > 100 events, broadcasts alerts to all WebSocket clients
RunSessionVerticle In-memory runner participation: join/leave with name + location, publishes snapshots every 5s
WebSocketVerticle HTTP REST API + 2 WebSocket endpoints, static file serving, CORS handling

External APIs

1. Airqoon Air Quality API

Real-time air quality sensor data for Bursa province.

Base URL: https://map.airqoon.com/bursa/api/v2

Endpoint Method Description
/devices GET Returns all registered sensor devices (id, name, location, tenant)
/devices/{deviceId}/telemetries?startTime={ms}&endTime={ms} GET Returns measurement data for a device within a time range

Headers:

Accept: application/json
Referer: https://map.airqoon.com/bursa/

Device Response:

{
  "DeviceId": "63b70e27efae9654696a5517",
  "Name": "BursaBB4 - Yıldırım",
  "TenantName": "Bursa Metropolitan Municipality",
  "Location": {
    "Latitude": 40.185791,
    "Longitude": 29.084729
  }
}

Telemetry Response:

{
  "PM25": [
    { "Value": 12.5, "Date": "2026-06-04T16:18:24.499Z" }
  ],
  "PM10": [
    { "Value": 28.0, "Date": "2026-06-04T16:18:24.499Z" }
  ],
  "Temperature": [...],
  "Humidity": [...]
}

Monitored Sensors (28 total):

Sensor Location Operator
BursaBB4 Yildirim Bursa Metropolitan Municipality
BursaBB5 Gemlik Bursa Metropolitan Municipality
BursaBB3 Osmangazi Demirtas Bursa Metropolitan Municipality
BursaBB6 Osmangazi Kent Meydani Bursa Metropolitan Municipality
BursaBB7 Osmangazi Demirtaspasa (Hanlar Bolgesi) Bursa Metropolitan Municipality
BursaBB8 Gursu Bursa Metropolitan Municipality
BursaBB9 Kestel Bursa Metropolitan Municipality
BursaBB2 Kestel Barakfakih Bursa Metropolitan Municipality
BursaBB13 Orhaneli Bursa Metropolitan Municipality
BursaBB14 Gemlik Atatepe Bursa Metropolitan Municipality
BursaBB1 Orhangazi Bursa Metropolitan Municipality
BursaBB10 Nilufer Balkan Bursa Metropolitan Municipality
BursaBB11 Mustafakemalpasa Bursa Metropolitan Municipality
BursaBB12 Karacabey Bursa Metropolitan Municipality
BursaBB15 Keles Bursa Metropolitan Municipality
airQuality_lena Inegol Turankoy Inegol Municipality
airQuality_modi Inegol Merkez Inegol Municipality
airQuality_merx, vult, novo, veru, vide, fera Inegol (various) Inegol Municipality
MudanyaB-1 to B-5 Mudanya (5 sensors) Mudanya Municipality

2. OpenWeatherMap API

Current weather conditions for runner locations.

Base URL: https://api.openweathermap.org/data/2.5/weather

Parameter Value
lat Latitude
lon Longitude
appid API key (env: OPENWEATHER_API_KEY)
units metric (Celsius)
lang tr (Turkish descriptions)

Response (parsed fields):

{
  "temperature": 21.4,
  "feelsLike": 20.8,
  "humidity": 55,
  "pressure": 1013,
  "windSpeed": 3.5,
  "cloudiness": 0,
  "condition": "Clear",
  "description": "acik",
  "icon": "01d"
}

WebSocket Protocol

/ws/aqi — Air Quality Stream

On connect: Server sends a full snapshot of all station data.

{
  "type": "snapshot",
  "stations": [...],
  "devices": [...]
}

Every 60s: Server pushes individual station updates as telemetry is fetched.

{
  "type": "stationUpdate",
  "data": {
    "deviceId": "63b70e27...",
    "name": "BursaBB4 - Yıldırım",
    "tenant": "Bursa Metropolitan Municipality",
    "lat": 40.185791,
    "lon": 29.084729,
    "latest": {
      "pm25": 12.5,
      "pm10": 28.0,
      "temperatureraw": 22.3,
      "humidity": 45.2
    },
    "history": {
      "pm25": [{"v": 12.5, "t": "2026-06-04T16:18:24Z"}, ...]
    }
  }
}

Alert (PM2.5 > 100):

{
  "type": "alert",
  "station": "BursaBB4 - Yıldırım",
  "pm25": 105.3,
  "pm10": 180.0
}

/ws/runs — Runner Participation Stream

On connect: Server sends current participants + available locations.

{
  "type": "snapshot",
  "participants": [
    {
      "id": "uuid",
      "name": "Kadir",
      "locationId": "yildirim",
      "locationName": "Yildirim",
      "district": "Yildirim",
      "joinedAt": 1780594624652
    }
  ],
  "locations": [...]
}

When someone joins:

{ "type": "joined", "participant": {...} }

When someone leaves:

{ "type": "left", "participant": {...} }

Every 5s tick:

{ "type": "tick", "participants": [...] }

REST API

Method Endpoint Body Response
GET /api/stations All stations with latest telemetry
GET /api/stations/:id Single station data
GET /api/devices Raw device list from Airqoon
GET /api/locations 12 preset runner locations
GET /api/active All active participants
POST /api/join {"name": "Kadir", "locationId": "yildirim"} {"id": "uuid", "name": "Kadir", "locationName": "Yildirim", ...}
POST /api/leave {"id": "uuid"} {"ok": true}
POST /api/refresh Triggers immediate sensor data fetch

EventBus Topics

Internal communication between verticles via Vert.x EventBus:

Topic Pattern Publisher Consumer
aqi.devices Publish DataFetcher WebSocket
aqi.data.station Publish DataFetcher WebSocket, Alert
aqi.alert Publish DataFetcher Alert
aqi.ws.alert Publish Alert WebSocket
aqi.fetch.now Request/Reply WebSocket DataFetcher
weather.fetch Request/Reply RunSession Weather
weather.data Publish Weather (broadcast)
run.join Request/Reply WebSocket RunSession
run.leave Request/Reply WebSocket RunSession
run.active Request/Reply WebSocket RunSession
run.started Publish RunSession WebSocket
run.finished Publish RunSession WebSocket
run.tick Publish (5s) RunSession WebSocket

Tech Stack

Component Technology
Runtime Java 17
Framework Vert.x 4.5.11 (event-driven, non-blocking)
HTTP/WS Server Vert.x Web + Vert.x Core
HTTP Client Vert.x Web Client (async, non-blocking)
Inter-verticle Communication Vert.x EventBus (in-process pub/sub + req/rep)
Map Leaflet.js + OpenStreetMap (no API key required)
Packaging Maven Shade Plugin (fat JAR)

Build & Run

# Build
mvn clean package

# Run
java -jar target/vertx-event-bus-bursa-hava-kalite-izleme-1.0.0.jar

# With custom OpenWeatherMap key
OPENWEATHER_API_KEY=your_key java -jar target/vertx-event-bus-bursa-hava-kalite-izleme-1.0.0.jar

Pages:

  • http://localhost:8080 — Air Quality Dashboard (28 stations, live sparklines)
  • http://localhost:8080/runners.html — Runner Participation Tracker
  • http://localhost:8080/map.html — Station Map (Leaflet)

Project Structure

src/main/java/com/example/hava/
  MainVerticle.java                  — Bootstrap, deploys all verticles
  dto/
    PresetLocation.java              — 12 preset Bursa locations
    WeatherDTO.java                  — OpenWeatherMap response mapping
    RunSessionDTO.java               — Runner session model
    LocationConditionsDTO.java       — Location snapshot (AQI + weather + runner count)
  verticle/
    DataFetcherVerticle.java         — Airqoon API poller (60s interval)
    WeatherFetcherVerticle.java      — OpenWeatherMap poller (5min interval)
    AlertVerticle.java               — PM2.5 threshold alerts
    RunSessionVerticle.java          — Join/leave participation tracker
    WebSocketVerticle.java           — HTTP REST + WebSocket server

src/main/resources/webroot/
  index.html                         — AQI Dashboard (vanilla JS)
  runners.html                       — Runner Tracker (vanilla JS)
  map.html                           — Leaflet Map (vanilla JS)

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages