lt-public-transport-sdk 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -12
- package/dist/gtfs/index.d.ts +2 -2
- package/dist/gtfs/index.d.ts.map +1 -1
- package/dist/gtfs/index.js +2 -2
- package/dist/gtfs/index.js.map +1 -1
- package/dist/gtfs/parser.d.ts +49 -2
- package/dist/gtfs/parser.d.ts.map +1 -1
- package/dist/gtfs/parser.js +328 -2
- package/dist/gtfs/parser.js.map +1 -1
- package/dist/gtfs/sync.d.ts +26 -2
- package/dist/gtfs/sync.d.ts.map +1 -1
- package/dist/gtfs/sync.js +205 -12
- package/dist/gtfs/sync.js.map +1 -1
- package/dist/index.d.ts +73 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +132 -1
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +84 -12
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +96 -32
- package/dist/schemas.js.map +1 -1
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
+
[](https://www.buymeacoffee.com/gmacev)
|
|
2
|
+
|
|
1
3
|
# lt-public-transport-sdk
|
|
2
4
|
|
|
3
5
|
A robust, type-safe TypeScript SDK for accessing **real-time public transport data** in Lithuanian cities.
|
|
4
6
|
|
|
5
7
|
It handles the complexity of parsing raw GPS streams (HTML/CSV), normalizing coordinate formats, handling text encodings (Windows-1257), and merging live data with static GTFS schedules (routes, stops).
|
|
6
8
|
|
|
7
|
-

|
|
8
|
-

|
|
9
|
-

|
|
10
|
-
|
|
11
9
|
## 🚀 Why use this SDK?
|
|
12
10
|
|
|
13
11
|
Raw public transport data in Lithuania (from stops.lt) is fragmented and messy:
|
|
@@ -170,14 +168,20 @@ const cities = client.getCities(); // includes 'marijampole'
|
|
|
170
168
|
|
|
171
169
|
#### Methods
|
|
172
170
|
|
|
173
|
-
| Method
|
|
174
|
-
|
|
|
175
|
-
| **`getVehicles(city)`**
|
|
176
|
-
| **`sync(city, force?)`**
|
|
177
|
-
| **`getRoutes(city)`**
|
|
178
|
-
| **`getStops(city)`**
|
|
179
|
-
| **`
|
|
180
|
-
| **`
|
|
171
|
+
| Method | Returns | Description |
|
|
172
|
+
| ---------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
|
|
173
|
+
| **`getVehicles(city)`** | `Promise<Vehicle[]>` | Fetches real-time vehicle positions. If `autoEnrich` is true, ensures GTFS data is synced and merges it. |
|
|
174
|
+
| **`sync(city, force?)`** | `Promise<SyncResult>` | Manually downloads/updates GTFS static data (routes/stops). Throttled to 60s unless `force=true`. |
|
|
175
|
+
| **`getRoutes(city)`** | `Promise<Route[]>` | Returns the list of static routes from GTFS. Requires prior `sync()`. |
|
|
176
|
+
| **`getStops(city)`** | `Promise<Stop[]>` | Returns the list of static stops from GTFS. Requires prior `sync()`. |
|
|
177
|
+
| **`getTrips(city)`** | `Promise<Trip[]>` | Returns all trips with route/service/shape linkage. Requires prior `sync()`. |
|
|
178
|
+
| **`getShapes(city)`** | `Promise<Map<string, ShapePoint[]>>` | Returns route polylines grouped by shape ID. Requires prior `sync()`. |
|
|
179
|
+
| **`getCalendar(city)`** | `Promise<Calendar[]>` | Returns service calendars (which days services operate). Requires prior `sync()`. |
|
|
180
|
+
| **`getCalendarDates(city)`** | `Promise<CalendarDate[]>` | Returns calendar exceptions (holidays, special dates). Requires prior `sync()`. |
|
|
181
|
+
| **`getAgencies(city)`** | `Promise<Agency[]>` | Returns transit agency information. Requires prior `sync()`. |
|
|
182
|
+
| **`getSchedule(city)`** | `Promise<Map<string, StopTime[]>>` | Returns stop times grouped by trip ID (⚠️ large dataset). Requires prior `sync()`. |
|
|
183
|
+
| **`getCities()`** | `CityId[]` | Returns a list of all supported city identifiers. |
|
|
184
|
+
| **`getCityConfig(city)`** | `CityConfig` | Returns configuration details (tier, URLs) for a specific city. |
|
|
181
185
|
|
|
182
186
|
### Key Types
|
|
183
187
|
|
|
@@ -277,6 +281,159 @@ interface Stop {
|
|
|
277
281
|
}
|
|
278
282
|
```
|
|
279
283
|
|
|
284
|
+
#### `Trip` (GTFS)
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
interface Trip {
|
|
288
|
+
/** Unique trip ID */
|
|
289
|
+
readonly id: string;
|
|
290
|
+
|
|
291
|
+
/** Route ID reference */
|
|
292
|
+
readonly routeId: string;
|
|
293
|
+
|
|
294
|
+
/** Service/calendar ID */
|
|
295
|
+
readonly serviceId: string;
|
|
296
|
+
|
|
297
|
+
/** Trip destination headsign */
|
|
298
|
+
readonly headsign: string;
|
|
299
|
+
|
|
300
|
+
/** Trip short name (optional) */
|
|
301
|
+
readonly shortName: string | null;
|
|
302
|
+
|
|
303
|
+
/** Direction (0 or 1) */
|
|
304
|
+
readonly directionId: number | null;
|
|
305
|
+
|
|
306
|
+
/** Shape ID reference (for drawing route path) */
|
|
307
|
+
readonly shapeId: string | null;
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### `ShapePoint` (GTFS)
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
interface ShapePoint {
|
|
315
|
+
/** Shape ID */
|
|
316
|
+
readonly shapeId: string;
|
|
317
|
+
|
|
318
|
+
/** Latitude */
|
|
319
|
+
readonly latitude: number;
|
|
320
|
+
|
|
321
|
+
/** Longitude */
|
|
322
|
+
readonly longitude: number;
|
|
323
|
+
|
|
324
|
+
/** Point sequence in shape */
|
|
325
|
+
readonly sequence: number;
|
|
326
|
+
|
|
327
|
+
/** Distance traveled along shape (optional) */
|
|
328
|
+
readonly distanceTraveled: number | null;
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### `Calendar` (GTFS)
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
interface Calendar {
|
|
336
|
+
/** Service ID */
|
|
337
|
+
readonly serviceId: string;
|
|
338
|
+
|
|
339
|
+
/** Operates on Mondays */
|
|
340
|
+
readonly monday: boolean;
|
|
341
|
+
|
|
342
|
+
/** Operates on Tuesdays */
|
|
343
|
+
readonly tuesday: boolean;
|
|
344
|
+
|
|
345
|
+
/** Operates on Wednesdays */
|
|
346
|
+
readonly wednesday: boolean;
|
|
347
|
+
|
|
348
|
+
/** Operates on Thursdays */
|
|
349
|
+
readonly thursday: boolean;
|
|
350
|
+
|
|
351
|
+
/** Operates on Fridays */
|
|
352
|
+
readonly friday: boolean;
|
|
353
|
+
|
|
354
|
+
/** Operates on Saturdays */
|
|
355
|
+
readonly saturday: boolean;
|
|
356
|
+
|
|
357
|
+
/** Operates on Sundays */
|
|
358
|
+
readonly sunday: boolean;
|
|
359
|
+
|
|
360
|
+
/** Service start date (ISO format YYYY-MM-DD) */
|
|
361
|
+
readonly startDate: string;
|
|
362
|
+
|
|
363
|
+
/** Service end date (ISO format YYYY-MM-DD) */
|
|
364
|
+
readonly endDate: string;
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### `CalendarDate` (GTFS)
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
interface CalendarDate {
|
|
372
|
+
/** Service ID */
|
|
373
|
+
readonly serviceId: string;
|
|
374
|
+
|
|
375
|
+
/** Exception date (ISO format YYYY-MM-DD) */
|
|
376
|
+
readonly date: string;
|
|
377
|
+
|
|
378
|
+
/** Exception type */
|
|
379
|
+
readonly exceptionType: "added" | "removed";
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### `Agency` (GTFS)
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
interface Agency {
|
|
387
|
+
/** Agency ID (optional if only one agency) */
|
|
388
|
+
readonly id: string | null;
|
|
389
|
+
|
|
390
|
+
/** Agency name */
|
|
391
|
+
readonly name: string;
|
|
392
|
+
|
|
393
|
+
/** Agency website URL */
|
|
394
|
+
readonly url: string;
|
|
395
|
+
|
|
396
|
+
/** Agency timezone */
|
|
397
|
+
readonly timezone: string;
|
|
398
|
+
|
|
399
|
+
/** Agency language code */
|
|
400
|
+
readonly language: string | null;
|
|
401
|
+
|
|
402
|
+
/** Agency phone number */
|
|
403
|
+
readonly phone: string | null;
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
#### `StopTime` (GTFS)
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
interface StopTime {
|
|
411
|
+
/** Trip ID */
|
|
412
|
+
readonly tripId: string;
|
|
413
|
+
|
|
414
|
+
/** Stop ID */
|
|
415
|
+
readonly stopId: string;
|
|
416
|
+
|
|
417
|
+
/** Arrival time (HH:MM:SS format, may exceed 24:00:00) */
|
|
418
|
+
readonly arrivalTime: string;
|
|
419
|
+
|
|
420
|
+
/** Departure time (HH:MM:SS format, may exceed 24:00:00) */
|
|
421
|
+
readonly departureTime: string;
|
|
422
|
+
|
|
423
|
+
/** Stop sequence (0-based) */
|
|
424
|
+
readonly sequence: number;
|
|
425
|
+
|
|
426
|
+
/** Stop headsign override */
|
|
427
|
+
readonly stopHeadsign: string | null;
|
|
428
|
+
|
|
429
|
+
/** Pickup type (0=regular, 1=none, etc.) */
|
|
430
|
+
readonly pickupType: number | null;
|
|
431
|
+
|
|
432
|
+
/** Drop-off type (0=regular, 1=none, etc.) */
|
|
433
|
+
readonly dropOffType: number | null;
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
280
437
|
## 💾 Caching & Best Practices
|
|
281
438
|
|
|
282
439
|
By default, GTFS data (routes & stops) is cached in your system's temporary directory (`os.tmpdir()/lt-transport-sdk-cache`).
|
|
@@ -338,3 +495,9 @@ npm run test:integration
|
|
|
338
495
|
## 📄 License
|
|
339
496
|
|
|
340
497
|
MIT
|
|
498
|
+
|
|
499
|
+
## Support
|
|
500
|
+
|
|
501
|
+
If you found this SDK useful for your project, consider buying me a coffee! It helps me keep the reverse-engineering efforts going.
|
|
502
|
+
|
|
503
|
+
<a href="https://www.buymeacoffee.com/gmacev" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
package/dist/gtfs/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* GTFS module exports
|
|
3
3
|
* @module gtfs
|
|
4
4
|
*/
|
|
5
|
-
export { parseRoutesContent, parseStopsContent, } from './parser.js';
|
|
6
|
-
export { syncGtfs, loadGtfsCache, loadCachedRoutes, loadCachedStops, type SyncOptions, type GtfsCache, } from './sync.js';
|
|
5
|
+
export { parseRoutesContent, parseStopsContent, parseTripsContent, parseShapesContent, parseCalendarContent, parseCalendarDatesContent, parseAgencyContent, parseStopTimesContent, } from './parser.js';
|
|
6
|
+
export { syncGtfs, loadGtfsCache, loadCachedRoutes, loadCachedStops, loadCachedTrips, loadCachedShapes, loadCachedCalendar, loadCachedCalendarDates, loadCachedAgencies, loadCachedStopTimes, type SyncOptions, type GtfsCache, } from './sync.js';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/gtfs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/gtfs/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/gtfs/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,WAAW,EAChB,KAAK,SAAS,GACf,MAAM,WAAW,CAAC"}
|
package/dist/gtfs/index.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* GTFS module exports
|
|
3
3
|
* @module gtfs
|
|
4
4
|
*/
|
|
5
|
-
export { parseRoutesContent, parseStopsContent, } from './parser.js';
|
|
6
|
-
export { syncGtfs, loadGtfsCache, loadCachedRoutes, loadCachedStops, } from './sync.js';
|
|
5
|
+
export { parseRoutesContent, parseStopsContent, parseTripsContent, parseShapesContent, parseCalendarContent, parseCalendarDatesContent, parseAgencyContent, parseStopTimesContent, } from './parser.js';
|
|
6
|
+
export { syncGtfs, loadGtfsCache, loadCachedRoutes, loadCachedStops, loadCachedTrips, loadCachedShapes, loadCachedCalendar, loadCachedCalendarDates, loadCachedAgencies, loadCachedStopTimes, } from './sync.js';
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/gtfs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gtfs/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gtfs/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,GAGpB,MAAM,WAAW,CAAC"}
|
package/dist/gtfs/parser.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GTFS file parsers for routes.txt
|
|
2
|
+
* GTFS file parsers for routes.txt, stops.txt, trips.txt, shapes.txt,
|
|
3
|
+
* calendar.txt, calendar_dates.txt, agency.txt, and stop_times.txt
|
|
3
4
|
* @module gtfs/parser
|
|
4
5
|
*/
|
|
5
|
-
import type { Route, Stop } from '../types.js';
|
|
6
|
+
import type { Route, Stop, Trip, ShapePoint, Calendar, CalendarDate, Agency, StopTime } from '../types.js';
|
|
6
7
|
/**
|
|
7
8
|
* Parse routes.txt content into a Map keyed by route short name.
|
|
8
9
|
*
|
|
@@ -36,4 +37,50 @@ export declare function parseRoutesContent(content: string): Map<string, Route>;
|
|
|
36
37
|
* @returns Array of Stop objects
|
|
37
38
|
*/
|
|
38
39
|
export declare function parseStopsContent(content: string): Stop[];
|
|
40
|
+
/**
|
|
41
|
+
* Parse trips.txt content into a Map keyed by trip_id.
|
|
42
|
+
*
|
|
43
|
+
* @param content - Raw trips.txt content
|
|
44
|
+
* @returns Map from trip_id to Trip object
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseTripsContent(content: string): Map<string, Trip>;
|
|
47
|
+
/**
|
|
48
|
+
* Parse shapes.txt content into a Map grouped by shape_id.
|
|
49
|
+
* Points within each shape are sorted by sequence.
|
|
50
|
+
*
|
|
51
|
+
* @param content - Raw shapes.txt content
|
|
52
|
+
* @returns Map from shape_id to array of ShapePoint objects
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseShapesContent(content: string): Map<string, ShapePoint[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Parse calendar.txt content into a Map keyed by service_id.
|
|
57
|
+
*
|
|
58
|
+
* @param content - Raw calendar.txt content
|
|
59
|
+
* @returns Map from service_id to Calendar object
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseCalendarContent(content: string): Map<string, Calendar>;
|
|
62
|
+
/**
|
|
63
|
+
* Parse calendar_dates.txt content into an array of CalendarDate objects.
|
|
64
|
+
*
|
|
65
|
+
* @param content - Raw calendar_dates.txt content
|
|
66
|
+
* @returns Array of CalendarDate objects
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseCalendarDatesContent(content: string): CalendarDate[];
|
|
69
|
+
/**
|
|
70
|
+
* Parse agency.txt content into an array of Agency objects.
|
|
71
|
+
*
|
|
72
|
+
* @param content - Raw agency.txt content
|
|
73
|
+
* @returns Array of Agency objects
|
|
74
|
+
*/
|
|
75
|
+
export declare function parseAgencyContent(content: string): Agency[];
|
|
76
|
+
/**
|
|
77
|
+
* Parse stop_times.txt content into a Map grouped by trip_id.
|
|
78
|
+
* Stop times within each trip are sorted by sequence.
|
|
79
|
+
*
|
|
80
|
+
* Note: This file can be large (~25MB for Vilnius). Use appropriate memory management.
|
|
81
|
+
*
|
|
82
|
+
* @param content - Raw stop_times.txt content
|
|
83
|
+
* @returns Map from trip_id to array of StopTime objects
|
|
84
|
+
*/
|
|
85
|
+
export declare function parseStopTimesContent(content: string): Map<string, StopTime[]>;
|
|
39
86
|
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/gtfs/parser.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/gtfs/parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAe,MAAM,aAAa,CAAC;AA6ExH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAsDtE;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,CAiDzD;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAgDpE;AAMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAwD7E;AAcD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAmD3E;AAMD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CA0CzE;AAMD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CA6C5D;AAMD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAyD9E"}
|
package/dist/gtfs/parser.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GTFS file parsers for routes.txt
|
|
2
|
+
* GTFS file parsers for routes.txt, stops.txt, trips.txt, shapes.txt,
|
|
3
|
+
* calendar.txt, calendar_dates.txt, agency.txt, and stop_times.txt
|
|
3
4
|
* @module gtfs/parser
|
|
4
5
|
*/
|
|
5
6
|
import { GTFS_ROUTE_TYPE_MAP } from '../types.js';
|
|
6
7
|
import { cleanTextField } from '../utils/index.js';
|
|
7
|
-
import { gtfsRouteSchema, gtfsStopSchema } from '../schemas.js';
|
|
8
|
+
import { gtfsRouteSchema, gtfsStopSchema, gtfsTripSchema, gtfsShapeSchema, gtfsCalendarSchema, gtfsCalendarDateSchema, gtfsAgencySchema, gtfsStopTimeSchema, } from '../schemas.js';
|
|
8
9
|
// =============================================================================
|
|
9
10
|
// CSV Parsing Utilities
|
|
10
11
|
// =============================================================================
|
|
@@ -186,4 +187,329 @@ export function parseStopsContent(content) {
|
|
|
186
187
|
}
|
|
187
188
|
return stops;
|
|
188
189
|
}
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// Trips Parser
|
|
192
|
+
// =============================================================================
|
|
193
|
+
/**
|
|
194
|
+
* Parse trips.txt content into a Map keyed by trip_id.
|
|
195
|
+
*
|
|
196
|
+
* @param content - Raw trips.txt content
|
|
197
|
+
* @returns Map from trip_id to Trip object
|
|
198
|
+
*/
|
|
199
|
+
export function parseTripsContent(content) {
|
|
200
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
201
|
+
if (lines.length < 2) {
|
|
202
|
+
return new Map();
|
|
203
|
+
}
|
|
204
|
+
const firstLine = lines[0];
|
|
205
|
+
if (firstLine === undefined || firstLine === '') {
|
|
206
|
+
return new Map();
|
|
207
|
+
}
|
|
208
|
+
const headers = parseCSVLine(firstLine);
|
|
209
|
+
const headerMap = buildHeaderMap(headers);
|
|
210
|
+
const trips = new Map();
|
|
211
|
+
for (let i = 1; i < lines.length; i++) {
|
|
212
|
+
const line = lines[i];
|
|
213
|
+
if (line === undefined || line === '')
|
|
214
|
+
continue;
|
|
215
|
+
const row = parseCSVLine(line);
|
|
216
|
+
try {
|
|
217
|
+
const rowObject = buildRowObject(row, headerMap);
|
|
218
|
+
const parseResult = gtfsTripSchema.safeParse(rowObject);
|
|
219
|
+
if (!parseResult.success) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const validated = parseResult.data;
|
|
223
|
+
const trip = {
|
|
224
|
+
id: validated.trip_id,
|
|
225
|
+
routeId: validated.route_id,
|
|
226
|
+
serviceId: validated.service_id,
|
|
227
|
+
headsign: cleanTextField(validated.trip_headsign),
|
|
228
|
+
directionId: validated.direction_id ?? null,
|
|
229
|
+
shapeId: validated.shape_id ?? null,
|
|
230
|
+
blockId: validated.block_id ?? null,
|
|
231
|
+
};
|
|
232
|
+
trips.set(trip.id, trip);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return trips;
|
|
239
|
+
}
|
|
240
|
+
// =============================================================================
|
|
241
|
+
// Shapes Parser
|
|
242
|
+
// =============================================================================
|
|
243
|
+
/**
|
|
244
|
+
* Parse shapes.txt content into a Map grouped by shape_id.
|
|
245
|
+
* Points within each shape are sorted by sequence.
|
|
246
|
+
*
|
|
247
|
+
* @param content - Raw shapes.txt content
|
|
248
|
+
* @returns Map from shape_id to array of ShapePoint objects
|
|
249
|
+
*/
|
|
250
|
+
export function parseShapesContent(content) {
|
|
251
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
252
|
+
if (lines.length < 2) {
|
|
253
|
+
return new Map();
|
|
254
|
+
}
|
|
255
|
+
const firstLine = lines[0];
|
|
256
|
+
if (firstLine === undefined || firstLine === '') {
|
|
257
|
+
return new Map();
|
|
258
|
+
}
|
|
259
|
+
const headers = parseCSVLine(firstLine);
|
|
260
|
+
const headerMap = buildHeaderMap(headers);
|
|
261
|
+
const shapes = new Map();
|
|
262
|
+
for (let i = 1; i < lines.length; i++) {
|
|
263
|
+
const line = lines[i];
|
|
264
|
+
if (line === undefined || line === '')
|
|
265
|
+
continue;
|
|
266
|
+
const row = parseCSVLine(line);
|
|
267
|
+
try {
|
|
268
|
+
const rowObject = buildRowObject(row, headerMap);
|
|
269
|
+
const parseResult = gtfsShapeSchema.safeParse(rowObject);
|
|
270
|
+
if (!parseResult.success) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const validated = parseResult.data;
|
|
274
|
+
const point = {
|
|
275
|
+
shapeId: validated.shape_id,
|
|
276
|
+
latitude: validated.shape_pt_lat,
|
|
277
|
+
longitude: validated.shape_pt_lon,
|
|
278
|
+
sequence: validated.shape_pt_sequence,
|
|
279
|
+
distanceTraveled: validated.shape_dist_traveled ?? null,
|
|
280
|
+
};
|
|
281
|
+
const existing = shapes.get(point.shapeId);
|
|
282
|
+
if (existing !== undefined) {
|
|
283
|
+
existing.push(point);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
shapes.set(point.shapeId, [point]);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Sort points within each shape by sequence
|
|
294
|
+
for (const points of shapes.values()) {
|
|
295
|
+
points.sort((a, b) => a.sequence - b.sequence);
|
|
296
|
+
}
|
|
297
|
+
return shapes;
|
|
298
|
+
}
|
|
299
|
+
// =============================================================================
|
|
300
|
+
// Calendar Parser
|
|
301
|
+
// =============================================================================
|
|
302
|
+
/**
|
|
303
|
+
* Helper to parse GTFS date format (YYYYMMDD) to ISO format (YYYY-MM-DD).
|
|
304
|
+
*/
|
|
305
|
+
function parseGtfsDate(dateStr) {
|
|
306
|
+
if (dateStr.length !== 8)
|
|
307
|
+
return dateStr;
|
|
308
|
+
return `${dateStr.slice(0, 4)}-${dateStr.slice(4, 6)}-${dateStr.slice(6, 8)}`;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Parse calendar.txt content into a Map keyed by service_id.
|
|
312
|
+
*
|
|
313
|
+
* @param content - Raw calendar.txt content
|
|
314
|
+
* @returns Map from service_id to Calendar object
|
|
315
|
+
*/
|
|
316
|
+
export function parseCalendarContent(content) {
|
|
317
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
318
|
+
if (lines.length < 2) {
|
|
319
|
+
return new Map();
|
|
320
|
+
}
|
|
321
|
+
const firstLine = lines[0];
|
|
322
|
+
if (firstLine === undefined || firstLine === '') {
|
|
323
|
+
return new Map();
|
|
324
|
+
}
|
|
325
|
+
const headers = parseCSVLine(firstLine);
|
|
326
|
+
const headerMap = buildHeaderMap(headers);
|
|
327
|
+
const calendars = new Map();
|
|
328
|
+
for (let i = 1; i < lines.length; i++) {
|
|
329
|
+
const line = lines[i];
|
|
330
|
+
if (line === undefined || line === '')
|
|
331
|
+
continue;
|
|
332
|
+
const row = parseCSVLine(line);
|
|
333
|
+
try {
|
|
334
|
+
const rowObject = buildRowObject(row, headerMap);
|
|
335
|
+
const parseResult = gtfsCalendarSchema.safeParse(rowObject);
|
|
336
|
+
if (!parseResult.success) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const validated = parseResult.data;
|
|
340
|
+
const calendar = {
|
|
341
|
+
serviceId: validated.service_id,
|
|
342
|
+
monday: validated.monday === 1,
|
|
343
|
+
tuesday: validated.tuesday === 1,
|
|
344
|
+
wednesday: validated.wednesday === 1,
|
|
345
|
+
thursday: validated.thursday === 1,
|
|
346
|
+
friday: validated.friday === 1,
|
|
347
|
+
saturday: validated.saturday === 1,
|
|
348
|
+
sunday: validated.sunday === 1,
|
|
349
|
+
startDate: parseGtfsDate(validated.start_date),
|
|
350
|
+
endDate: parseGtfsDate(validated.end_date),
|
|
351
|
+
};
|
|
352
|
+
calendars.set(calendar.serviceId, calendar);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return calendars;
|
|
359
|
+
}
|
|
360
|
+
// =============================================================================
|
|
361
|
+
// Calendar Dates Parser
|
|
362
|
+
// =============================================================================
|
|
363
|
+
/**
|
|
364
|
+
* Parse calendar_dates.txt content into an array of CalendarDate objects.
|
|
365
|
+
*
|
|
366
|
+
* @param content - Raw calendar_dates.txt content
|
|
367
|
+
* @returns Array of CalendarDate objects
|
|
368
|
+
*/
|
|
369
|
+
export function parseCalendarDatesContent(content) {
|
|
370
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
371
|
+
if (lines.length < 2) {
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
const firstLine = lines[0];
|
|
375
|
+
if (firstLine === undefined || firstLine === '') {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
const headers = parseCSVLine(firstLine);
|
|
379
|
+
const headerMap = buildHeaderMap(headers);
|
|
380
|
+
const calendarDates = [];
|
|
381
|
+
for (let i = 1; i < lines.length; i++) {
|
|
382
|
+
const line = lines[i];
|
|
383
|
+
if (line === undefined || line === '')
|
|
384
|
+
continue;
|
|
385
|
+
const row = parseCSVLine(line);
|
|
386
|
+
try {
|
|
387
|
+
const rowObject = buildRowObject(row, headerMap);
|
|
388
|
+
const parseResult = gtfsCalendarDateSchema.safeParse(rowObject);
|
|
389
|
+
if (!parseResult.success) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const validated = parseResult.data;
|
|
393
|
+
calendarDates.push({
|
|
394
|
+
serviceId: validated.service_id,
|
|
395
|
+
date: parseGtfsDate(validated.date),
|
|
396
|
+
exceptionType: validated.exception_type === 1 ? 'added' : 'removed',
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return calendarDates;
|
|
404
|
+
}
|
|
405
|
+
// =============================================================================
|
|
406
|
+
// Agency Parser
|
|
407
|
+
// =============================================================================
|
|
408
|
+
/**
|
|
409
|
+
* Parse agency.txt content into an array of Agency objects.
|
|
410
|
+
*
|
|
411
|
+
* @param content - Raw agency.txt content
|
|
412
|
+
* @returns Array of Agency objects
|
|
413
|
+
*/
|
|
414
|
+
export function parseAgencyContent(content) {
|
|
415
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
416
|
+
if (lines.length < 2) {
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
const firstLine = lines[0];
|
|
420
|
+
if (firstLine === undefined || firstLine === '') {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
const headers = parseCSVLine(firstLine);
|
|
424
|
+
const headerMap = buildHeaderMap(headers);
|
|
425
|
+
const agencies = [];
|
|
426
|
+
for (let i = 1; i < lines.length; i++) {
|
|
427
|
+
const line = lines[i];
|
|
428
|
+
if (line === undefined || line === '')
|
|
429
|
+
continue;
|
|
430
|
+
const row = parseCSVLine(line);
|
|
431
|
+
try {
|
|
432
|
+
const rowObject = buildRowObject(row, headerMap);
|
|
433
|
+
const parseResult = gtfsAgencySchema.safeParse(rowObject);
|
|
434
|
+
if (!parseResult.success) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const validated = parseResult.data;
|
|
438
|
+
agencies.push({
|
|
439
|
+
id: validated.agency_id ?? '',
|
|
440
|
+
name: cleanTextField(validated.agency_name),
|
|
441
|
+
url: validated.agency_url,
|
|
442
|
+
timezone: validated.agency_timezone,
|
|
443
|
+
language: validated.agency_lang ?? null,
|
|
444
|
+
phone: validated.agency_phone ?? null,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return agencies;
|
|
452
|
+
}
|
|
453
|
+
// =============================================================================
|
|
454
|
+
// Stop Times Parser
|
|
455
|
+
// =============================================================================
|
|
456
|
+
/**
|
|
457
|
+
* Parse stop_times.txt content into a Map grouped by trip_id.
|
|
458
|
+
* Stop times within each trip are sorted by sequence.
|
|
459
|
+
*
|
|
460
|
+
* Note: This file can be large (~25MB for Vilnius). Use appropriate memory management.
|
|
461
|
+
*
|
|
462
|
+
* @param content - Raw stop_times.txt content
|
|
463
|
+
* @returns Map from trip_id to array of StopTime objects
|
|
464
|
+
*/
|
|
465
|
+
export function parseStopTimesContent(content) {
|
|
466
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
467
|
+
if (lines.length < 2) {
|
|
468
|
+
return new Map();
|
|
469
|
+
}
|
|
470
|
+
const firstLine = lines[0];
|
|
471
|
+
if (firstLine === undefined || firstLine === '') {
|
|
472
|
+
return new Map();
|
|
473
|
+
}
|
|
474
|
+
const headers = parseCSVLine(firstLine);
|
|
475
|
+
const headerMap = buildHeaderMap(headers);
|
|
476
|
+
const stopTimes = new Map();
|
|
477
|
+
for (let i = 1; i < lines.length; i++) {
|
|
478
|
+
const line = lines[i];
|
|
479
|
+
if (line === undefined || line === '')
|
|
480
|
+
continue;
|
|
481
|
+
const row = parseCSVLine(line);
|
|
482
|
+
try {
|
|
483
|
+
const rowObject = buildRowObject(row, headerMap);
|
|
484
|
+
const parseResult = gtfsStopTimeSchema.safeParse(rowObject);
|
|
485
|
+
if (!parseResult.success) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const validated = parseResult.data;
|
|
489
|
+
const stopTime = {
|
|
490
|
+
tripId: validated.trip_id,
|
|
491
|
+
stopId: validated.stop_id,
|
|
492
|
+
arrivalTime: validated.arrival_time,
|
|
493
|
+
departureTime: validated.departure_time,
|
|
494
|
+
sequence: validated.stop_sequence,
|
|
495
|
+
headsign: validated.stop_headsign ?? null,
|
|
496
|
+
};
|
|
497
|
+
const existing = stopTimes.get(stopTime.tripId);
|
|
498
|
+
if (existing !== undefined) {
|
|
499
|
+
existing.push(stopTime);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
stopTimes.set(stopTime.tripId, [stopTime]);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// Sort stop times within each trip by sequence
|
|
510
|
+
for (const times of stopTimes.values()) {
|
|
511
|
+
times.sort((a, b) => a.sequence - b.sequence);
|
|
512
|
+
}
|
|
513
|
+
return stopTimes;
|
|
514
|
+
}
|
|
189
515
|
//# sourceMappingURL=parser.js.map
|