Skip to content

Commit b668e9d

Browse files
areeyanclaude
andcommitted
Live map of every bus, a Fleet tab of every model, bus speeds fixed
- Live tab: every bus in Auckland on one MapLibre layer, coloured by operator, gliding between fixes; search by route, fleet number or model; filters for electric, double-deckers and operators; tap for route, destination, delay, speed, load and model - Fleet tab: every model sorted by how many are out now; each has a page with a drawing, specs, history, fleet numbers and a live map of all of them - Fleet table rebuilt from the AT Metro Wiki: 18 models, ~73% of buses identified (was ~29%); tri-axles drawn with three axles - Bus speeds: AT reports km/h for buses (m/s for trains); the app multiplied both by 3.6, so buses showed e.g. 172 km/h instead of 48 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 95fd752 commit b668e9d

17 files changed

Lines changed: 1262 additions & 76 deletions

‎README.md‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,30 @@ Britomart and 8664 to Waikowhai. Stops and route can be changed in Settings.
3838
fixes and pointing where it's heading. Tap it for full screen, then tap a bus
3939
to see what it is. Pull down anywhere to refresh.
4040

41+
## Live
42+
43+
Every bus in Auckland on one map, about a thousand at a time, coloured by
44+
operator and gliding between GPS fixes. Zoom in and each one shows its heading
45+
and route number.
46+
47+
- **Search** by route (`27` finds the 27H, 27W and 27T), fleet number
48+
(`NB5075`) or model (`eT12`), and the map frames what it found.
49+
- **Filters** for electric buses, double-deckers or one operator, with live counts.
50+
- **Tap a bus** for its route and destination, how late it's running, its
51+
speed, how full it is and what model it is. Tap the model for its page.
52+
53+
## Fleet
54+
55+
Every bus model on the network (the CRRC eT12 MAX, Geely C13E, Enviro500 and
56+
the rest), sorted by how many are out right now.
57+
58+
- **Each model has its own page:** a drawing of it (double-deckers, tri-axles,
59+
electrics), specs such as length, seats, motor and top speed, a short
60+
history, the fleet numbers each operator uses, and a live map of every one of
61+
them on the road, with a list you can tap to find each bus.
62+
- Buses whose fleet numbers aren't in the list yet are grouped under **Not
63+
identified yet**, with their own map.
64+
4165
## Trains
4266

4367
A live map of the post-CRL network, drawn after AT's official "Ngā Tereina"
@@ -83,7 +107,8 @@ Transit data comes straight from the
83107
| Bus and train GPS | `realtime/legacy/vehiclelocations?tripid=` / `?vehicleid=` (all trains are 59xxx) |
84108
| Train destination and stops | `gtfs/v3/trips/{id}` and `.../stoptimes` |
85109
| Station departures | stoptrips for each platform (platforms from `parent_station`) |
86-
| Bus operator and model | the vehicle label (`TR3884`) looked up in `app/src/main/assets/fleet.tsv` |
110+
| Every bus (Live, Fleet) | `realtime/legacy/vehiclelocations`, the whole feed (~80 KB gzipped) |
111+
| Bus operator and model | the vehicle label (`TR3884`) looked up in `app/src/main/assets/fleet.tsv` and `models.json` |
87112

88113
Train GPS fixes are snapped onto the nearest station-to-station stretch of the
89114
train's own line on the schematic. `tools/gen_data.py` generates the map
@@ -92,14 +117,22 @@ geometry, the station/platform table and the 27H's stop lists into
92117

93118
AT_API_KEY=... python tools/gen_data.py
94119

95-
The app polls only while it's on screen: buses every 30 s, trains every 15 s.
120+
The app polls only while it's on screen: buses every 30 s, trains every 15 s,
121+
and the whole network every 20 s, only while Live or Fleet is open.
122+
123+
AT's feed reports train speeds in m/s, as GTFS-realtime specifies, but bus
124+
speeds in km/h. Both were checked against how far vehicles actually moved
125+
between fixes.
96126

97127
### The fleet list
98128

99129
AT's feed doesn't say what model a bus is, only its fleet number with the
100130
operator's code in front (NB NZ Bus, GB Go Bus, RT Ritchies, HE Howick &
101131
Eastern, TR Tranzurban, BA Bayes, WB Waiheke). `fleet.tsv` maps fleet number
102-
ranges to models. Every CI build runs `tools/fleet_survey.py`, which lists each
132+
ranges to models, and `models.json` describes each model. Both come from the
133+
[AT Metro Wiki](https://atmetro.fandom.com)'s model and operator pages (CC BY-SA).
134+
They cover about three quarters of the buses on the road. Every CI build runs
135+
`tools/fleet_survey.py`, which lists each
103136
range on the road right now, the routes it's running and whether the table
104137
knows it, so gaps are easy to fill in:
105138

‎app/src/main/assets/fleet.tsv‎

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,81 @@
11
# What bus is coming: AT's realtime feed labels every bus with its operator's
22
# code and fleet number ("TR3884", "NB5014"). This maps fleet number ranges to
3-
# the model. tools/fleet_survey.py (a step in every CI build) lists the ranges
4-
# on the road that aren't covered here yet.
3+
# a model in models.json. tools/fleet_survey.py (a step in every CI build)
4+
# lists the ranges on the road that aren't covered here yet.
55
#
6-
# Tab-separated: prefix, first, last, model, power (electric|diesel), decks (1|2)
7-
# Sources: AT Metro Wiki model pages, operator announcements, and the ranges
8-
# the survey sees running each route.
6+
# Tab-separated: prefix, first, last, model id (see models.json)
7+
# Source: the AT Metro Wiki's model and operator pages (atmetro.fandom.com,
8+
# CC BY-SA), checked against the ranges the survey sees on the road.
99

10-
# Tranzurban: Scania + BCI double-deckers from 2018 (NX2), CRRCs for the
11-
# Northwestern from 2024-25 (eT12 on the 11T/11W, eD12 on the WX1)
12-
TR 3300 3399 Scania K320UB diesel 1
13-
TR 3500 3599 BCI CitiRider FBC6123 diesel 2
14-
TR 3810 3829 CRRC eT12 MAX electric 1
15-
TR 3860 3889 CRRC eD12 MAX electric 2
10+
# NZ Bus (Kinetic)
11+
NB 1400 1499 man17223
12+
NB 2101 2168 k270
13+
NB 2400 2499 man17223
14+
NB 4001 4354 e200
15+
NB 5001 5077 e500
16+
NB 5400 5400 ed12
17+
NB 5720 5721 et12
18+
NB 5752 5786 et12
19+
NB 5800 5842 et12
20+
NB 5900 5979 c13e
21+
NB 6001 6012 es12
22+
NB 6101 6120 c10e
1623

17-
# NZ Bus (Kinetic): the Enviro200 fleet, CRRC electrics from 2022-23, and
18-
# Geely electrics from 2024 (24B/24R, 76, 18)
19-
NB 4001 4354 Alexander Dennis Enviro200 diesel 1
20-
NB 5752 5786 CRRC eT12 MAX electric 1
21-
NB 5800 5842 CRRC eT12 MAX electric 1
22-
NB 5900 5979 Geely C13E electric 1
24+
# Go Bus (Kinetic)
25+
GB 5201 5237 man16250
26+
GB 5255 5293 man16250
27+
GB 5401 5434 man18290
28+
GB 8901 8909 e12
29+
GB 8936 8936 e12
2330

24-
# Ritchies: the Enviro500 MMC double-deckers (Northern Express and AT Metro)
25-
RT 1356 1378 Alexander Dennis Enviro500 MMC diesel 2
31+
# Ritchies, including the ex-Birkenhead Transport and ex-Pavlovich fleets
32+
RT 356 359 k270
33+
RT 449 449 k270
34+
RT 487 487 k270
35+
RT 568 571 k270
36+
RT 639 639 k270
37+
RT 1132 1132 e500mmc
38+
RT 1246 1246 k270
39+
RT 1322 1355 e200mmc
40+
RT 1356 1386 e500mmc
41+
RT 1411 1417 man18290
42+
RT 1431 1431 man17223
43+
RT 1432 1437 man16250
44+
RT 1439 1441 man16250
45+
RT 1443 1447 man16250
46+
RT 1494 1497 man18290
47+
RT 3015 3015 e13
48+
RT 4006 4017 e200
49+
RT 4018 4019 e200mmc
50+
RT 4020 4022 e13
51+
RT 4024 4029 e200mmc
52+
RT 4377 4378 man17223
53+
RT 4601 4636 k320
54+
RT 4701 4705 e200
55+
RT 4720 4723 e200mmc
56+
RT 4801 4833 k320
57+
RT 5601 5602 e200evxlb
58+
RT 7001 7024 et12
59+
RT 7201 7290 et12
60+
RT 7401 7477 es12
61+
RT 9795 9799 e200
62+
RT 9807 9810 man16250
63+
RT 9836 9837 k270
64+
65+
# Howick & Eastern (Transdev)
66+
HE 156 182 e200
67+
HE 218 223 man17223
68+
HE 500 518 e500
69+
HE 604 611 e13
70+
71+
# Tranzurban (Tranzit)
72+
TR 3300 3399 k320
73+
TR 3500 3599 bci6123
74+
TR 3810 3828 et12
75+
TR 3860 3885 ed12
76+
TR 3897 3897 et12
77+
TR 3898 3898 e13
78+
79+
# Waiheke Bus Company, Bayes
80+
WB 150 157 e200
81+
BA 157 158 e500mmc

‎app/src/main/assets/models.json‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
[
2+
{
3+
"id": "et12", "name": "CRRC eT12 MAX", "maker": "CRRC", "electric": true, "decks": 1, "axles": 3,
4+
"specs": [["Length", "12.7 m"], ["Seats", "37–40"], ["Motor", "240 kW"], ["Top speed", "80 km/h"], ["In Auckland since", "2022"]],
5+
"about": "A long three-axle electric bus from Zhejiang CRRC, and the most common electric bus in Auckland. NZ Bus brought in the first ones in 2022–23, Tranzurban's arrived in late 2024 for the 11T and 11W, and Ritchies has been adding batches through 2025 and 2026. NZ Bus's NB5778–5786 wear the Tāmaki Link colours."
6+
},
7+
{
8+
"id": "ed12", "name": "CRRC eD12 MAX", "maker": "CRRC", "electric": true, "decks": 2,
9+
"specs": [["Length", "12.0 m"], ["Height", "4.25 m"], ["Motor", "350 kW"], ["Top speed", "90 km/h"], ["In Auckland since", "2024"]],
10+
"about": "Auckland's first fully electric double-decker. Kinetic unveiled it in March 2024 and NB5400 went into service on the 18, 25B and 25L that April. Tranzurban's fleet has run the 12 and WX1 since April 2025: the ocean-blue ones on the 12, the Shore-liveried ones on the WX1."
11+
},
12+
{
13+
"id": "es12", "name": "CRRC eS12 MAX", "maker": "CRRC", "electric": true, "decks": 1, "axles": 2,
14+
"specs": [["Length", "11.3 m"], ["Seats", "31–33"], ["Motor", "240 kW"], ["Top speed", "80 km/h"], ["In Auckland since", "2021"]],
15+
"about": "The shorter, two-axle CRRC electric. It started on the City Link with NZ Bus in 2021 (two of those now do the Inner Link), and Ritchies runs a big fleet of them."
16+
},
17+
{
18+
"id": "c13e", "name": "Geely C13E", "maker": "Geely", "electric": true, "decks": 1, "axles": 3,
19+
"specs": [["Built by", "Farizon (Geely)"], ["In Auckland since", "2024"]],
20+
"about": "A three-axle electric bus from Farizon, Geely's commercial-vehicle brand. Kinetic's NZ Bus put the first ones into service in June 2024, and a second batch with bigger batteries joined in 2025 on routes between the city and the North Shore."
21+
},
22+
{
23+
"id": "c10e", "name": "Geely C10E", "maker": "Geely", "electric": true, "decks": 1,
24+
"specs": [["Built by", "Farizon (Geely)"], ["Runs", "Inner and Outer Link"]],
25+
"about": "The shorter sibling of the Geely C13E, from the same Farizon factory. Kinetic's NZ Bus runs them on the Inner Link and Outer Link; NB6103 was the first to carry passengers on the Outer Link."
26+
},
27+
{
28+
"id": "e12", "name": "Yutong E12", "maker": "Yutong", "electric": true, "decks": 1, "axles": 2,
29+
"specs": [["Seats", "26 on the Airport Link"], ["In Auckland since", "2020"]],
30+
"about": "A two-axle electric bus from Zhengzhou Yutong. Go Bus brought them in for the Airport Link in 2020; luggage racks down both sides cut the seats to 26 (the city version seats 34–36)."
31+
},
32+
{
33+
"id": "e13", "name": "Yutong E13", "maker": "Yutong", "electric": true, "decks": 1, "axles": 3,
34+
"specs": [["Seats", "36"], ["Carries", "up to 78"], ["In Auckland since", "2020"]],
35+
"about": "Yutong's three-axle electric. Howick & Eastern introduced it on route 70 in 2020; Ritchies and Tranzurban run a few as well."
36+
},
37+
{
38+
"id": "e200", "name": "Alexander Dennis Enviro200", "maker": "Alexander Dennis", "electric": false, "decks": 1, "axles": 2,
39+
"specs": [["Seats", "35–37"], ["Doors", "2"], ["In Auckland since", "2011"]],
40+
"about": "The diesel workhorse of NZ Bus. It arrived in 2011 on the City Link, and at its peak NZ Bus ran more than 300 of them across Auckland, Tauranga and Wellington. Bodies came from Kiwi Bus Builders and Alexander Dennis; the last ones for mainland Auckland were built in 2015."
41+
},
42+
{
43+
"id": "e200mmc", "name": "Alexander Dennis Enviro200 MMC", "maker": "Alexander Dennis", "electric": false, "decks": 1, "axles": 2,
44+
"specs": [["In Auckland since", "2017"]],
45+
"about": "The second-generation Enviro200 (MMC stands for Major Model Change), in Auckland since 2017. Ritchies also runs a shorter single-door version, the Enviro200 Midi."
46+
},
47+
{
48+
"id": "e200evxlb", "name": "BYD ADL Enviro200EV XLB", "maker": "BYD", "electric": true, "decks": 1, "axles": 3,
49+
"specs": [["Length", "12.6 m"], ["Battery", "348 kWh"], ["Seats", "36"], ["Carries", "up to 78"], ["In Auckland since", "2021"]],
50+
"about": "A three-axle electric built as a partnership: BYD's batteries and driveline under an Alexander Dennis body. It seats 36 and carries up to 78 people, with two wheelchair spaces."
51+
},
52+
{
53+
"id": "e500", "name": "Alexander Dennis Enviro500", "maker": "Alexander Dennis", "electric": false, "decks": 2, "axles": 3,
54+
"specs": [["Length", "12.5 m"], ["Height", "4.2 m"], ["Seats", "55 up, 31–36 down"], ["Engine", "Cummins ISL9"]],
55+
"about": "The big three-axle diesel double-decker. NZ Bus's 5000 series work the busiest isthmus routes (the 18, 25B, 25L, 27H, 27W, 30 and 75), and their size makes them a favourite for full-body ads. Bodies were assembled by Kiwi Bus Builders or Zhuhai Granton."
56+
},
57+
{
58+
"id": "e500mmc", "name": "Alexander Dennis Enviro500 MMC", "maker": "Alexander Dennis", "electric": false, "decks": 2, "axles": 3,
59+
"specs": [["Length", "12.5 m"], ["Height", "4.2 m"], ["Seats", "55 up, 31–36 down"], ["Engine", "Cummins ISL9"]],
60+
"about": "The updated Enviro500 body, bought by Ritchies (including Birkenhead Transport) and Bayes, mostly from 2018 on, and assembled by Zhuhai Granton. Same size as the original: 12.5 m long and 4.2 m tall."
61+
},
62+
{
63+
"id": "bci6123", "name": "BCI CitiRider FBC6123", "maker": "BCI", "electric": false, "decks": 2, "axles": 3,
64+
"specs": [["In Auckland since", "2018"]],
65+
"about": "A three-axle double-decker from Australian builder BCI. Tranzurban brought them in July 2018 when it took over the Northern Express NX2."
66+
},
67+
{
68+
"id": "k320", "name": "Scania K320UB", "maker": "Scania", "electric": false, "decks": 1, "axles": 3,
69+
"specs": [["Engine", "320 hp"]],
70+
"about": "A Swedish chassis with a 320 hp engine, always built three-axle in Auckland to keep road-user charges down. The body depends on the operator: Kiwi Bus Builders (46–49 seats) on Tranzurban's NX2 buses, and Bonluck on the ex-Pavlovich fleet, which has big screens inside showing the next stop."
71+
},
72+
{
73+
"id": "k270", "name": "Scania K270UB", "maker": "Scania", "electric": false, "decks": 1, "axles": 3,
74+
"specs": [["Engine", "270 hp"]],
75+
"about": "Scania's 270 hp chassis, three-axle like most big diesels here. NZ Bus's 2100 series have DesignLine bodies (the last eleven have the Enviroline design); Ritchies' were bodied by Kiwi Bus Builders."
76+
},
77+
{
78+
"id": "man16250", "name": "MAN 16.250", "maker": "MAN", "electric": false, "decks": 1, "axles": 2,
79+
"specs": [["Seats", "37–43"], ["Body", "Enviroline"]],
80+
"about": "A two-axle MAN with the Enviroline body built in Rolleston, Canterbury, by Global Bus Ventures (formerly DesignLine). Go Bus runs most of them; Ritchies has some, partly from Birkenhead Transport."
81+
},
82+
{
83+
"id": "man18290", "name": "MAN 18.290", "maker": "MAN", "electric": false, "decks": 1, "axles": 3,
84+
"specs": [["Engine", "290 hp"], ["Weight rating", "18 t"], ["Body", "Enviroline"]],
85+
"about": "A three-axle MAN (the name means 18 tonnes and 290 hp), mostly with Global Bus Ventures' Enviroline body; one Ritchies bus has a Malaysian Gemilang body instead. Go Bus and Ritchies run them."
86+
},
87+
{
88+
"id": "man17223", "name": "MAN 17.223", "maker": "MAN", "electric": false, "decks": 1, "axles": 3,
89+
"specs": [["Built", "2002–2007"]],
90+
"about": "A tag-axle bus built by DesignLine in Ashburton between 2002 and 2007, first bought by Stagecoach for Auckland. Most of NZ Bus's have been retired in favour of electrics, but a handful are still running."
91+
}
92+
]

‎app/src/main/java/nz/aryan/akllive/AppViewModel.kt‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import kotlinx.coroutines.launch
1414
import kotlinx.coroutines.withTimeoutOrNull
1515
import nz.aryan.akllive.data.AtApi
1616
import nz.aryan.akllive.data.BusRepo
17+
import nz.aryan.akllive.data.BusTrip
18+
import nz.aryan.akllive.data.FleetState
19+
import nz.aryan.akllive.data.LiveRepo
1720
import nz.aryan.akllive.data.Nz
1821
import nz.aryan.akllive.data.StationDeparture
1922
import nz.aryan.akllive.data.StopBoard
@@ -60,6 +63,7 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
6063
private val api = AtApi { prefs.apiKey }
6164
private val busRepo = BusRepo(api)
6265
private val trainRepo = TrainRepo(api)
66+
private val liveRepo = LiveRepo(api)
6367

6468
private val _boards = MutableStateFlow(prefs.stops.map { StopBoard(it) })
6569
val boards: StateFlow<List<StopBoard>> = _boards
@@ -85,20 +89,65 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
8589
private val _station = MutableStateFlow<Pair<Int, List<StationDeparture>?>?>(null)
8690
val station: StateFlow<Pair<Int, List<StationDeparture>?>?> = _station
8791

92+
/** every bus on the network, polled only while a screen that shows them is open */
93+
private val _fleet = MutableStateFlow(FleetState())
94+
val fleet: StateFlow<FleetState> = _fleet
95+
private val _busTrip = MutableStateFlow<BusTrip?>(null)
96+
val busTrip: StateFlow<BusTrip?> = _busTrip
97+
/** the model page open on the Fleet tab ("unknown" for the unidentified buses) */
98+
val fleetModel = MutableStateFlow<String?>(null)
99+
private var liveWatchers = 0
100+
88101
private val kick = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
102+
private val liveKick = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
89103
@Volatile private var visible = false
90104

91105
init {
92106
Fleet.load(app)
93107
viewModelScope.launch { loop(30_000) { refreshBuses() } }
94108
viewModelScope.launch { loop(15_000) { refreshTrains() } }
95109
viewModelScope.launch { loop(900_000) { Weather.fetch()?.let { _weather.value = it } } }
110+
viewModelScope.launch {
111+
while (true) {
112+
while (!visible || liveWatchers <= 0) delay(400)
113+
refreshLive()
114+
withTimeoutOrNull(20_000) { liveKick.first() }
115+
}
116+
}
117+
}
118+
119+
/** Screens showing every bus call this while they're up; the feed is only fetched then. */
120+
fun watchLive(on: Boolean) {
121+
liveWatchers = maxOf(0, liveWatchers + if (on) 1 else -1)
122+
if (on) liveKick.tryEmit(Unit)
123+
}
124+
125+
private suspend fun refreshLive() {
126+
try {
127+
_fleet.value = FleetState(liveRepo.all(), Nz.nowSec(), loading = false)
128+
} catch (e: Exception) {
129+
_fleet.value = _fleet.value.copy(loading = false, error = e.message)
130+
}
131+
}
132+
133+
/** A bus was tapped on the big map: look up where it's going. */
134+
fun selectBus(tripId: String?) {
135+
if (_busTrip.value?.tripId == tripId && tripId != null) return
136+
_busTrip.value = tripId?.let { BusTrip(it) }
137+
if (tripId == null) return
138+
viewModelScope.launch {
139+
val t = try { liveRepo.trip(tripId) } catch (_: Exception) { return@launch }
140+
if (_busTrip.value?.tripId == tripId) _busTrip.value = t
141+
}
96142
}
97143

98144
/** Poll only while the app is on screen; refresh straight away when it comes back. */
99145
fun setVisible(v: Boolean) {
100146
visible = v
101-
if (v) refresh()
147+
if (v) {
148+
refresh()
149+
liveKick.tryEmit(Unit)
150+
}
102151
}
103152

104153
fun refresh() { kick.tryEmit(Unit) }

0 commit comments

Comments
 (0)