gtfs 4.5.1 → 4.7.0

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/@types/index.d.ts CHANGED
@@ -152,7 +152,7 @@ export function getAgencies(
152
152
  query?: SqlWhere,
153
153
  fields?: SqlSelect,
154
154
  sortBy?: SqlOrderBy,
155
- options?: QueryOptions
155
+ options?: QueryOptions,
156
156
  ): SqlResults;
157
157
 
158
158
  /**
@@ -162,7 +162,7 @@ export function getAreas(
162
162
  query?: SqlWhere,
163
163
  fields?: SqlSelect,
164
164
  sortBy?: SqlOrderBy,
165
- options?: QueryOptions
165
+ options?: QueryOptions,
166
166
  ): SqlResults;
167
167
 
168
168
  /**
@@ -172,7 +172,7 @@ export function getAttributions(
172
172
  query?: SqlWhere,
173
173
  fields?: SqlSelect,
174
174
  sortBy?: SqlOrderBy,
175
- options?: QueryOptions
175
+ options?: QueryOptions,
176
176
  ): SqlResults;
177
177
 
178
178
  /**
@@ -182,7 +182,7 @@ export function getRoutes(
182
182
  query?: SqlWhere,
183
183
  fields?: SqlSelect,
184
184
  sortBy?: SqlOrderBy,
185
- options?: QueryOptions
185
+ options?: QueryOptions,
186
186
  ): SqlResults;
187
187
 
188
188
  /**
@@ -192,7 +192,7 @@ export function getStops(
192
192
  query?: SqlWhere,
193
193
  fields?: SqlSelect,
194
194
  sortBy?: SqlOrderBy,
195
- options?: QueryOptions
195
+ options?: QueryOptions,
196
196
  ): SqlResults;
197
197
 
198
198
  /**
@@ -201,7 +201,7 @@ export function getStops(
201
201
  */
202
202
  export function getStopsAsGeoJSON(
203
203
  query?: SqlWhere,
204
- options?: QueryOptions
204
+ options?: QueryOptions,
205
205
  ): Promise<FeatureCollection<Geometry, { [name: string]: any }>>;
206
206
 
207
207
  /**
@@ -211,7 +211,7 @@ export function getStoptimes(
211
211
  query?: SqlWhere,
212
212
  fields?: SqlSelect,
213
213
  sortBy?: SqlOrderBy,
214
- options?: QueryOptions
214
+ options?: QueryOptions,
215
215
  ): SqlResults;
216
216
 
217
217
  /**
@@ -221,7 +221,7 @@ export function getTrips(
221
221
  query?: SqlWhere,
222
222
  fields?: SqlSelect,
223
223
  sortBy?: SqlOrderBy,
224
- options?: QueryOptions
224
+ options?: QueryOptions,
225
225
  ): SqlResults;
226
226
 
227
227
  /**
@@ -231,7 +231,7 @@ export function getShapes(
231
231
  query?: SqlWhere,
232
232
  fields?: SqlSelect,
233
233
  sortBy?: SqlOrderBy,
234
- options?: QueryOptions
234
+ options?: QueryOptions,
235
235
  ): SqlResults;
236
236
 
237
237
  /**
@@ -240,7 +240,7 @@ export function getShapes(
240
240
  */
241
241
  export function getShapesAsGeoJSON(
242
242
  query?: SqlWhere,
243
- options?: QueryOptions
243
+ options?: QueryOptions,
244
244
  ): FeatureCollection<Geometry, { [name: string]: any }>;
245
245
 
246
246
  /**
@@ -250,7 +250,7 @@ export function getCalendars(
250
250
  query?: SqlWhere,
251
251
  fields?: SqlSelect,
252
252
  sortBy?: SqlOrderBy,
253
- options?: QueryOptions
253
+ options?: QueryOptions,
254
254
  ): SqlResults;
255
255
 
256
256
  /**
@@ -260,7 +260,7 @@ export function getCalendarDates(
260
260
  query?: SqlWhere,
261
261
  fields?: SqlSelect,
262
262
  sortBy?: SqlOrderBy,
263
- options?: QueryOptions
263
+ options?: QueryOptions,
264
264
  ): SqlResults;
265
265
 
266
266
  /**
@@ -270,7 +270,7 @@ export function getFareAttributes(
270
270
  query?: SqlWhere,
271
271
  fields?: SqlSelect,
272
272
  sortBy?: SqlOrderBy,
273
- options?: QueryOptions
273
+ options?: QueryOptions,
274
274
  ): SqlResults;
275
275
 
276
276
  /**
@@ -280,7 +280,7 @@ export function getFareLegRules(
280
280
  query?: SqlWhere,
281
281
  fields?: SqlSelect,
282
282
  sortBy?: SqlOrderBy,
283
- options?: QueryOptions
283
+ options?: QueryOptions,
284
284
  ): SqlResults;
285
285
 
286
286
  /**
@@ -290,7 +290,7 @@ export function getFareProducts(
290
290
  query?: SqlWhere,
291
291
  fields?: SqlSelect,
292
292
  sortBy?: SqlOrderBy,
293
- options?: QueryOptions
293
+ options?: QueryOptions,
294
294
  ): SqlResults;
295
295
 
296
296
  /**
@@ -300,7 +300,7 @@ export function getFareRules(
300
300
  query?: SqlWhere,
301
301
  fields?: SqlSelect,
302
302
  sortBy?: SqlOrderBy,
303
- options?: QueryOptions
303
+ options?: QueryOptions,
304
304
  ): SqlResults;
305
305
 
306
306
  /**
@@ -310,7 +310,7 @@ export function getFareTransferRules(
310
310
  query?: SqlWhere,
311
311
  fields?: SqlSelect,
312
312
  sortBy?: SqlOrderBy,
313
- options?: QueryOptions
313
+ options?: QueryOptions,
314
314
  ): SqlResults;
315
315
 
316
316
  /**
@@ -320,7 +320,7 @@ export function getFeedInfo(
320
320
  query?: SqlWhere,
321
321
  fields?: SqlSelect,
322
322
  sortBy?: SqlOrderBy,
323
- options?: QueryOptions
323
+ options?: QueryOptions,
324
324
  ): SqlResults;
325
325
 
326
326
  /**
@@ -330,7 +330,7 @@ export function getFrequencies(
330
330
  query?: SqlWhere,
331
331
  fields?: SqlSelect,
332
332
  sortBy?: SqlOrderBy,
333
- options?: QueryOptions
333
+ options?: QueryOptions,
334
334
  ): SqlResults;
335
335
 
336
336
  /**
@@ -340,7 +340,7 @@ export function getLevels(
340
340
  query?: SqlWhere,
341
341
  fields?: SqlSelect,
342
342
  sortBy?: SqlOrderBy,
343
- options?: QueryOptions
343
+ options?: QueryOptions,
344
344
  ): SqlResults;
345
345
 
346
346
  /**
@@ -350,7 +350,7 @@ export function getPathways(
350
350
  query?: SqlWhere,
351
351
  fields?: SqlSelect,
352
352
  sortBy?: SqlOrderBy,
353
- options?: QueryOptions
353
+ options?: QueryOptions,
354
354
  ): SqlResults;
355
355
 
356
356
  /**
@@ -360,7 +360,7 @@ export function getTransfers(
360
360
  query?: SqlWhere,
361
361
  fields?: SqlSelect,
362
362
  sortBy?: SqlOrderBy,
363
- options?: QueryOptions
363
+ options?: QueryOptions,
364
364
  ): SqlResults;
365
365
 
366
366
  /**
@@ -370,7 +370,7 @@ export function getTranslations(
370
370
  query?: SqlWhere,
371
371
  fields?: SqlSelect,
372
372
  sortBy?: SqlOrderBy,
373
- options?: QueryOptions
373
+ options?: QueryOptions,
374
374
  ): SqlResults;
375
375
 
376
376
  /**
@@ -380,7 +380,7 @@ export function getStopAreas(
380
380
  query?: SqlWhere,
381
381
  fields?: SqlSelect,
382
382
  sortBy?: SqlOrderBy,
383
- options?: QueryOptions
383
+ options?: QueryOptions,
384
384
  ): SqlResults;
385
385
 
386
386
  /**
@@ -391,7 +391,7 @@ export function getCalendarAttributes(
391
391
  query?: SqlWhere,
392
392
  fields?: SqlSelect,
393
393
  sortBy?: SqlOrderBy,
394
- options?: QueryOptions
394
+ options?: QueryOptions,
395
395
  ): SqlResults;
396
396
 
397
397
  /**
@@ -402,7 +402,7 @@ export function getDirections(
402
402
  query?: SqlWhere,
403
403
  fields?: SqlSelect,
404
404
  sortBy?: SqlOrderBy,
405
- options?: QueryOptions
405
+ options?: QueryOptions,
406
406
  ): SqlResults;
407
407
 
408
408
  /**
@@ -413,7 +413,7 @@ export function getRouteAttributes(
413
413
  query?: SqlWhere,
414
414
  fields?: SqlSelect,
415
415
  sortBy?: SqlOrderBy,
416
- options?: QueryOptions
416
+ options?: QueryOptions,
417
417
  ): SqlResults;
418
418
 
419
419
  /**
@@ -424,7 +424,7 @@ export function getStopAttributes(
424
424
  query?: SqlWhere,
425
425
  fields?: SqlSelect,
426
426
  sortBy?: SqlOrderBy,
427
- options?: QueryOptions
427
+ options?: QueryOptions,
428
428
  ): SqlResults;
429
429
 
430
430
  /**
@@ -435,7 +435,7 @@ export function getTimetables(
435
435
  query?: SqlWhere,
436
436
  fields?: SqlSelect,
437
437
  sortBy?: SqlOrderBy,
438
- options?: QueryOptions
438
+ options?: QueryOptions,
439
439
  ): SqlResults;
440
440
 
441
441
  /**
@@ -446,7 +446,7 @@ export function getTimetableStopOrders(
446
446
  query?: SqlWhere,
447
447
  fields?: SqlSelect,
448
448
  sortBy?: SqlOrderBy,
449
- options?: QueryOptions
449
+ options?: QueryOptions,
450
450
  ): SqlResults;
451
451
 
452
452
  /**
@@ -457,7 +457,7 @@ export function getTimetablePages(
457
457
  query?: SqlWhere,
458
458
  fields?: SqlSelect,
459
459
  sortBy?: SqlOrderBy,
460
- options?: QueryOptions
460
+ options?: QueryOptions,
461
461
  ): SqlResults;
462
462
 
463
463
  /**
@@ -468,7 +468,7 @@ export function getTimetableNotes(
468
468
  query?: SqlWhere,
469
469
  fields?: SqlSelect,
470
470
  sortBy?: SqlOrderBy,
471
- options?: QueryOptions
471
+ options?: QueryOptions,
472
472
  ): SqlResults;
473
473
 
474
474
  /**
@@ -479,7 +479,7 @@ export function getTimetableNotesReferences(
479
479
  query?: SqlWhere,
480
480
  fields?: SqlSelect,
481
481
  sortBy?: SqlOrderBy,
482
- options?: QueryOptions
482
+ options?: QueryOptions,
483
483
  ): SqlResults;
484
484
 
485
485
  /**
@@ -490,7 +490,7 @@ export function getTripsDatedVehicleJourneys(
490
490
  query?: SqlWhere,
491
491
  fields?: SqlSelect,
492
492
  sortBy?: SqlOrderBy,
493
- options?: QueryOptions
493
+ options?: QueryOptions,
494
494
  ): SqlResults;
495
495
 
496
496
  /**
@@ -501,7 +501,7 @@ export function getServiceAlerts(
501
501
  query?: SqlWhere,
502
502
  fields?: SqlSelect,
503
503
  sortBy?: SqlOrderBy,
504
- options?: QueryOptions
504
+ options?: QueryOptions,
505
505
  ): SqlResults;
506
506
 
507
507
  /**
@@ -512,18 +512,18 @@ export function getTripUpdates(
512
512
  query?: SqlWhere,
513
513
  fields?: SqlSelect,
514
514
  sortBy?: SqlOrderBy,
515
- options?: QueryOptions
515
+ options?: QueryOptions,
516
516
  ): SqlResults;
517
517
 
518
518
  /**
519
519
  * Returns an array of GTFS Realtime stop time updates that match query parameters.
520
520
  * This only works if you configure GTFS Realtime import in node-gtfs.
521
521
  */
522
- export function getStopTimesUpdates(
522
+ export function getStopTimeUpdates(
523
523
  query?: SqlWhere,
524
524
  fields?: SqlSelect,
525
525
  sortBy?: SqlOrderBy,
526
- options?: QueryOptions
526
+ options?: QueryOptions,
527
527
  ): SqlResults;
528
528
 
529
529
  /**
@@ -534,7 +534,7 @@ export function getVehiclePositions(
534
534
  query?: SqlWhere,
535
535
  fields?: SqlSelect,
536
536
  sortBy?: SqlOrderBy,
537
- options?: QueryOptions
537
+ options?: QueryOptions,
538
538
  ): SqlResults;
539
539
 
540
540
  /**
@@ -542,5 +542,5 @@ export function getVehiclePositions(
542
542
  */
543
543
  export function advancedQuery(
544
544
  table?: SqlTableName,
545
- advancedQueryOptions?: AdvancedQueryOptions
545
+ advancedQueryOptions?: AdvancedQueryOptions,
546
546
  ): SqlResults;
package/CHANGELOG.md CHANGED
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.7.0] - 2024-02-09
9
+
10
+ ### Added
11
+
12
+ - Added `bounding_box_side_m` option to `getStops` and `getStopsAsGeoJSON` to find all stops within a bounding box.
13
+
14
+ ### Updated
15
+
16
+ - Dependency updates
17
+
18
+ ## [4.6.0] - 2024-01-17
19
+
20
+ ### Changed
21
+
22
+ - Renamed `getStopTimesUpdates` to `getStopTimeUpdates`.
23
+
24
+ ### Added
25
+ - Added `schedule_relationship`, `trip_start_time`, `direction_id` and `route_id` fields to stop_time_updates and trip_updates tables.
26
+
27
+ ### Updated
28
+
29
+ - Dependency updates
30
+
8
31
  ## [4.5.1] - 2023-11-09
9
32
 
10
33
  ### Updated
package/README.md CHANGED
@@ -737,6 +737,24 @@ const stops = getStops({
737
737
  const stops = getStops({
738
738
  shape_id: 'cal_sf_tam',
739
739
  });
740
+
741
+ /*
742
+ * `getStops` allows passing a `bounding_box_side_m` value in the options
743
+ * parameter object. If included, it will return all stops within a square
744
+ * bounding box around the `stop_lat` and `stop_lon` parameters passed to
745
+ * the query using the size in meters specified.
746
+ */
747
+ const stops = getStops(
748
+ {
749
+ stop_lat: 37.58764,
750
+ stop_lon: -122.36265
751
+ },
752
+ [],
753
+ [],
754
+ {
755
+ bounding_box_side_m: 1000
756
+ }
757
+ );
740
758
  ```
741
759
 
742
760
  #### getStopsAsGeoJSON(query, options)
@@ -753,6 +771,17 @@ const stopsGeojson = getStopsAsGeoJSON();
753
771
  const stopsGeojson = getStopsAsGeoJSON({
754
772
  route_id: 'Lo-16APR',
755
773
  });
774
+
775
+ // Get all stops within a 1000m bounding box as geoJSON
776
+ const stopsGeojson = getStopsAsGeoJSON(
777
+ {
778
+ stop_lat: 37.58764,
779
+ stop_lon: -122.36265
780
+ },
781
+ {
782
+ bounding_box_side_m: 1000
783
+ }
784
+ );
756
785
  ```
757
786
 
758
787
  #### getStoptimes(query, fields, sortBy, options)
@@ -1189,15 +1218,15 @@ import { getTripUpdates } from 'gtfs';
1189
1218
  const tripUpdates = getTripUpdates();
1190
1219
  ```
1191
1220
 
1192
- #### getStopTimesUpdates(query, fields, sortBy, options)
1221
+ #### getStopTimeUpdates(query, fields, sortBy, options)
1193
1222
 
1194
1223
  Returns an array of GTFS Realtime stop time updates that match query parameters. [Details on Stop Time Updates](https://gtfs.org/realtime/feed-entities/trip-updates/#stoptimeupdate)
1195
1224
 
1196
1225
  ```js
1197
- import { getStopTimesUpdates } from 'gtfs';
1226
+ import { getStopTimeUpdates } from 'gtfs';
1198
1227
 
1199
- // Get all stop times updates
1200
- const stopTimesUpdates = getStopTimesUpdates();
1228
+ // Get all stop time updates
1229
+ const stopTimeUpdates = getStopTimeUpdates();
1201
1230
  ```
1202
1231
 
1203
1232
  #### getVehiclePositions(query, fields, sortBy, options)
package/lib/gtfs/stops.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  formatOrderByClause,
8
8
  formatSelectClause,
9
9
  formatWhereClause,
10
+ formatWhereClauseBoundingBox,
10
11
  formatWhereClauses,
11
12
  } from '../utils.js';
12
13
  import { stopsToGeoJSON } from '../geojson-utils.js';
@@ -21,7 +22,7 @@ function buildTripSubquery(query) {
21
22
 
22
23
  function buildStoptimeSubquery(query) {
23
24
  return `SELECT DISTINCT stop_id FROM stop_times WHERE trip_id IN (${buildTripSubquery(
24
- query
25
+ query,
25
26
  )})`;
26
27
  }
27
28
 
@@ -39,13 +40,21 @@ export function getStops(query = {}, fields = [], orderBy = [], options = {}) {
39
40
  let whereClause = '';
40
41
  const orderByClause = formatOrderByClause(orderBy);
41
42
 
42
- const stopQuery = omit(query, [
43
+ const stopQueryOmitKeys = [
43
44
  'route_id',
44
45
  'trip_id',
45
46
  'service_id',
46
47
  'direction_id',
47
48
  'shape_id',
48
- ]);
49
+ ];
50
+
51
+ // If bounding_box_side_m is defined, search for stops inside a bounding box so omit `stop_lat` and `stop_lon`.
52
+ if (options.bounding_box_side_m !== undefined) {
53
+ stopQueryOmitKeys.push('stop_lat', 'stop_lon');
54
+ }
55
+
56
+ let stopQuery = omit(query, stopQueryOmitKeys);
57
+
49
58
  const tripQuery = pick(query, [
50
59
  'route_id',
51
60
  'trip_id',
@@ -55,9 +64,23 @@ export function getStops(query = {}, fields = [], orderBy = [], options = {}) {
55
64
  ]);
56
65
 
57
66
  const whereClauses = Object.entries(stopQuery).map(([key, value]) =>
58
- formatWhereClause(key, value)
67
+ formatWhereClause(key, value),
59
68
  );
60
69
 
70
+ if (
71
+ options.bounding_box_side_m !== undefined &&
72
+ query.stop_lat !== undefined &&
73
+ query.stop_lon !== undefined
74
+ ) {
75
+ whereClauses.push(
76
+ formatWhereClauseBoundingBox(
77
+ query.stop_lat,
78
+ query.stop_lon,
79
+ options.bounding_box_side_m,
80
+ ),
81
+ );
82
+ }
83
+
61
84
  if (Object.values(tripQuery).length > 0) {
62
85
  whereClauses.push(`stop_id IN (${buildStoptimeSubquery(tripQuery)})`);
63
86
  }
@@ -68,7 +91,7 @@ export function getStops(query = {}, fields = [], orderBy = [], options = {}) {
68
91
 
69
92
  return db
70
93
  .prepare(
71
- `${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
94
+ `${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`,
72
95
  )
73
96
  .all();
74
97
  }
@@ -96,7 +119,7 @@ export function getStopsAsGeoJSON(query = {}, options = {}) {
96
119
  const stopAttributes = getStopAttributes({ stop_id: stop.stop_id });
97
120
 
98
121
  stop.routes = orderBy(routes, (route) =>
99
- Number.parseInt(route.route_short_name, 10)
122
+ Number.parseInt(route.route_short_name, 10),
100
123
  );
101
124
  stop.agency_name = agencies[0].agency_name;
102
125
 
@@ -7,16 +7,16 @@ import {
7
7
  formatSelectClause,
8
8
  formatWhereClauses,
9
9
  } from '../utils.js';
10
- import stopTimeUpdates from '../../models/gtfs-realtime/stop-times-updates.js';
10
+ import stopTimeUpdates from '../../models/gtfs-realtime/stop-time-updates.js';
11
11
 
12
12
  /*
13
- * Returns an array of all stop times updates that match the query parameters.
13
+ * Returns an array of all stop time updates that match the query parameters.
14
14
  */
15
- export function getStopTimesUpdates(
15
+ export function getStopTimeUpdates(
16
16
  query = {},
17
17
  fields = [],
18
18
  orderBy = [],
19
- options = {}
19
+ options = {},
20
20
  ) {
21
21
  const db = options.db ?? openDb();
22
22
  const tableName = sqlString.escapeId(stopTimeUpdates.filenameBase);
@@ -26,7 +26,7 @@ export function getStopTimesUpdates(
26
26
 
27
27
  return db
28
28
  .prepare(
29
- `${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
29
+ `${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`,
30
30
  )
31
31
  .all();
32
32
  }
package/lib/gtfs.js CHANGED
@@ -50,7 +50,7 @@ import { getRiderships } from './gtfs-ride/riderships.js';
50
50
  import { getTripCapacities } from './gtfs-ride/trip-capacities.js';
51
51
 
52
52
  // GTFS-Realtime Filenames
53
- import { getStopTimesUpdates } from './gtfs-realtime/stop-times-updates.js';
53
+ import { getStopTimeUpdates } from './gtfs-realtime/stop-time-updates.js';
54
54
  import { getTripUpdates } from './gtfs-realtime/trip-updates.js';
55
55
  import { getVehiclePositions } from './gtfs-realtime/vehicle-positions.js';
56
56
  import { getServiceAlerts } from './gtfs-realtime/service-alerts.js';
@@ -191,8 +191,8 @@ export { _getTripCapacities as getTripCapacities };
191
191
  const _updateGtfsRealtime = updateGtfsRealtime;
192
192
  export { _updateGtfsRealtime as updateGtfsRealtime };
193
193
 
194
- const _getStopTimesUpdates = getStopTimesUpdates;
195
- export { _getStopTimesUpdates as getStopTimesUpdates };
194
+ const _getStopTimeUpdates = getStopTimeUpdates;
195
+ export { _getStopTimeUpdates as getStopTimeUpdates };
196
196
 
197
197
  const _getTripUpdates = getTripUpdates;
198
198
  export { _getTripUpdates as getTripUpdates };
package/lib/import.js CHANGED
@@ -9,7 +9,7 @@ import stripBomStream from 'strip-bom-stream';
9
9
  import { dir } from 'tmp-promise';
10
10
  import untildify from 'untildify';
11
11
  import mapSeries from 'promise-map-series';
12
- import gtfsrt from 'gtfs-realtime-bindings';
12
+ import GtfsRealtimeBindings from 'gtfs-realtime-bindings';
13
13
  import sqlString from 'sqlstring-sqlite';
14
14
 
15
15
  import models from '../models/models.js';
@@ -59,7 +59,18 @@ const downloadGtfsRealtimeData = async (url, headers) => {
59
59
  }
60
60
 
61
61
  const buffer = await response.arrayBuffer();
62
- return gtfsrt.transit_realtime.FeedMessage.decode(Buffer.from(buffer));
62
+ const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
63
+ new Uint8Array(buffer),
64
+ );
65
+ return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
66
+ enums: String,
67
+ longs: String,
68
+ bytes: String,
69
+ defaults: true,
70
+ arrays: true,
71
+ objects: true,
72
+ oneofs: true,
73
+ });
63
74
  };
64
75
 
65
76
  function getDescendantProp(obj, desc, defaultvalue) {
@@ -69,11 +80,19 @@ function getDescendantProp(obj, desc, defaultvalue) {
69
80
  const nextKey = arr.shift();
70
81
  if (nextKey.includes('[')) {
71
82
  const arrayKey = nextKey.match(/(\w*)\[(\d+)\]/);
72
- if (!obj[arrayKey[1]]) return defaultvalue;
73
- if (!obj[arrayKey[1]][arrayKey[2]]) return defaultvalue;
83
+ if (obj[arrayKey[1]] === undefined) {
84
+ return defaultvalue;
85
+ }
86
+
87
+ if (obj[arrayKey[1]][arrayKey[2]] === undefined) {
88
+ return defaultvalue;
89
+ }
90
+
74
91
  obj = obj[arrayKey[1]][arrayKey[2]];
75
92
  } else {
76
- if (!obj[nextKey]) return defaultvalue;
93
+ if (obj[nextKey] === undefined) {
94
+ return defaultvalue;
95
+ }
77
96
  obj = obj[nextKey];
78
97
  }
79
98
  }
@@ -87,11 +106,11 @@ const markRealtimeDataStale = (config, log) => {
87
106
  const db = openDb(config);
88
107
 
89
108
  log(`Marking GTFS-Realtime data as stale..`);
90
- db.prepare(`UPDATE vehicle_positions SET isUpdated=0`).run();
91
- db.prepare(`UPDATE trip_updates SET isUpdated=0`).run();
92
- db.prepare(`UPDATE stop_times_updates SET isUpdated=0`).run();
93
- db.prepare(`UPDATE service_alerts SET isUpdated=0`).run();
94
- db.prepare(`UPDATE service_alert_targets SET isUpdated=0`).run();
109
+ db.prepare(`UPDATE vehicle_positions SET is_updated=0`).run();
110
+ db.prepare(`UPDATE trip_updates SET is_updated=0`).run();
111
+ db.prepare(`UPDATE stop_time_updates SET is_updated=0`).run();
112
+ db.prepare(`UPDATE service_alerts SET is_updated=0`).run();
113
+ db.prepare(`UPDATE service_alert_targets SET is_updated=0`).run();
95
114
  log(`Marked GTFS-Realtime data as stale\r`, true);
96
115
  };
97
116
 
@@ -99,11 +118,11 @@ const cleanStaleRealtimeData = (config, log) => {
99
118
  const db = openDb(config);
100
119
 
101
120
  log(`Cleaning stale GTFS-RT data..`);
102
- db.prepare(`DELETE FROM vehicle_positions WHERE isUpdated=0`).run();
103
- db.prepare(`DELETE FROM trip_updates WHERE isUpdated=0`).run();
104
- db.prepare(`DELETE FROM stop_times_updates WHERE isUpdated=0`).run();
105
- db.prepare(`DELETE FROM service_alerts WHERE isUpdated=0`).run();
106
- db.prepare(`DELETE FROM service_alert_targets WHERE isUpdated=0`).run();
121
+ db.prepare(`DELETE FROM vehicle_positions WHERE is_updated=0`).run();
122
+ db.prepare(`DELETE FROM trip_updates WHERE is_updated=0`).run();
123
+ db.prepare(`DELETE FROM stop_time_updates WHERE is_updated=0`).run();
124
+ db.prepare(`DELETE FROM service_alerts WHERE is_updated=0`).run();
125
+ db.prepare(`DELETE FROM service_alert_targets WHERE is_updated=0`).run();
107
126
  log(`Cleaned stale GTFS-Realtime data\r`, true);
108
127
  };
109
128
 
@@ -115,8 +134,8 @@ const updateRealtimeData = async (task) => {
115
134
  (x) => x.filenameBase === 'vehicle_positions',
116
135
  ),
117
136
  trip_updates: models.find((x) => x.filenameBase === 'trip_updates'),
118
- stop_times_updates: models.find(
119
- (x) => x.filenameBase === 'stop_times_updates',
137
+ stop_time_updates: models.find(
138
+ (x) => x.filenameBase === 'stop_time_updates',
120
139
  ),
121
140
  service_alerts: models.find((x) => x.filenameBase === 'service_alerts'),
122
141
  service_alert_targets: models.find(
@@ -131,7 +150,7 @@ const updateRealtimeData = async (task) => {
131
150
  trip_updates: model.trip_updates.schema
132
151
  .map((column) => column.name)
133
152
  .join(', '),
134
- stop_times_updates: model.stop_times_updates.schema
153
+ stop_time_updates: model.stop_time_updates.schema
135
154
  .map((column) => column.name)
136
155
  .join(', '),
137
156
  service_alerts: model.service_alerts.schema
@@ -149,14 +168,20 @@ const updateRealtimeData = async (task) => {
149
168
  for (const realtimeUrl of task.realtime_urls) {
150
169
  task.log(`Downloading GTFS-Realtime from ${realtimeUrl}`);
151
170
  // eslint-disable-next-line no-await-in-loop
152
- const tripUpdateData = await downloadGtfsRealtimeData(
171
+ const gtfsRealtimeData = await downloadGtfsRealtimeData(
153
172
  realtimeUrl,
154
173
  task.realtime_headers,
155
174
  );
175
+
156
176
  task.log(`Download successful`);
157
177
 
178
+ if (!gtfsRealtimeData.entity) {
179
+ continue;
180
+ }
181
+
158
182
  let totalLineCount = 0;
159
- for (const entity of tripUpdateData.entity) {
183
+
184
+ for (const entity of gtfsRealtimeData.entity) {
160
185
  // Determine the type of GTFS-Realtime
161
186
  let gtfsRealtimeType = null;
162
187
  if (entity.vehicle) {
@@ -194,23 +219,23 @@ const updateRealtimeData = async (task) => {
194
219
 
195
220
  // Special processing for tripUpdates
196
221
  if (entity.tripUpdate) {
197
- const stopUpdateArray = [];
198
- for (const stopUpdate of entity.tripUpdate.stopTimeUpdate) {
199
- stopUpdate.parent = entity;
200
- const subValues = model.stop_times_updates.schema.map((column) =>
222
+ const stopTimeUpdateArray = [];
223
+ for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
224
+ stopTimeUpdate.parent = entity;
225
+ const subValues = model.stop_time_updates.schema.map((column) =>
201
226
  sqlString.escape(
202
- getDescendantProp(stopUpdate, column.source, column.default),
227
+ getDescendantProp(stopTimeUpdate, column.source, column.default),
203
228
  ),
204
229
  );
205
- stopUpdateArray.push(`(${subValues.join(', ')})`);
230
+ stopTimeUpdateArray.push(`(${subValues.join(', ')})`);
206
231
  totalLineCount++;
207
232
  }
208
233
 
209
234
  try {
210
235
  db.prepare(
211
- `REPLACE INTO ${model.stop_times_updates.filenameBase} (${
212
- fields.stop_times_updates
213
- }) VALUES ${stopUpdateArray.join(', ')}`,
236
+ `REPLACE INTO ${model.stop_time_updates.filenameBase} (${
237
+ fields.stop_time_updates
238
+ }) VALUES ${stopTimeUpdateArray.join(', ')}`,
214
239
  ).run();
215
240
  } catch (error) {
216
241
  task.warn('Import error: ' + error.message);
package/lib/utils.js CHANGED
@@ -12,7 +12,7 @@ export function validateConfigForImport(config) {
12
12
  for (const [index, agency] of config.agencies.entries()) {
13
13
  if (!agency.path && !agency.url) {
14
14
  throw new Error(
15
- `No Agency \`url\` or \`path\` specified in config for agency index ${index}.`
15
+ `No Agency \`url\` or \`path\` specified in config for agency index ${index}.`,
16
16
  );
17
17
  }
18
18
  }
@@ -75,7 +75,7 @@ export function formatSelectClause(fields) {
75
75
 
76
76
  const selectItem = Object.entries(fields)
77
77
  .map(
78
- (key) => `${sqlString.escapeId(key[0])} AS ${sqlString.escapeId(key[1])}`
78
+ (key) => `${sqlString.escapeId(key[0])} AS ${sqlString.escapeId(key[1])}`,
79
79
  )
80
80
  .join(', ');
81
81
  return `SELECT ${selectItem}`;
@@ -86,12 +86,48 @@ export function formatJoinClause(joinObject) {
86
86
  .map(
87
87
  (data) =>
88
88
  `${data.type ? data.type + ' JOIN' : 'INNER JOIN'} ${sqlString.escapeId(
89
- data.table
90
- )} ON ${data.on}`
89
+ data.table,
90
+ )} ON ${data.on}`,
91
91
  )
92
92
  .join(' ');
93
93
  }
94
94
 
95
+ function degree2radian(angle) {
96
+ return (angle * Math.PI) / 180;
97
+ }
98
+
99
+ function radian2degree(angle) {
100
+ return (angle / Math.PI) * 180;
101
+ }
102
+
103
+ /*
104
+ * Search inside GPS coordinates boundary box
105
+ * Distance unit: meters
106
+ * */
107
+ export function formatWhereClauseBoundingBox(
108
+ latitudeDegree,
109
+ longitudeDegree,
110
+ boundingBoxSideMeters,
111
+ ) {
112
+ const earthRadius = 6371000;
113
+ latitudeDegree = parseFloat(latitudeDegree);
114
+ longitudeDegree = parseFloat(longitudeDegree);
115
+
116
+ const latitudeRadian = degree2radian(latitudeDegree);
117
+ const radiusFromLatitude = Math.cos(latitudeRadian) * earthRadius;
118
+
119
+ // `boundingBoxSideMeters` is divided by 2 we are centering the coordinates in the middle of this square
120
+ const deltaLatitude = radian2degree(boundingBoxSideMeters / 2 / earthRadius);
121
+ const deltaLongitude = radian2degree(
122
+ boundingBoxSideMeters / 2 / radiusFromLatitude,
123
+ );
124
+
125
+ let query = `stop_lat BETWEEN ${latitudeDegree - deltaLatitude} AND ${latitudeDegree + deltaLatitude}`;
126
+ query += ` AND stop_lon BETWEEN ${longitudeDegree - deltaLongitude} AND ${longitudeDegree + deltaLongitude}`;
127
+
128
+ return query;
129
+ }
130
+
95
131
  export function formatWhereClause(key, value) {
96
132
  if (Array.isArray(value)) {
97
133
  let whereClause = `${sqlString.escapeId(key)} IN (${value
@@ -119,7 +155,7 @@ export function formatWhereClauses(query) {
119
155
  }
120
156
 
121
157
  const whereClauses = Object.entries(query).map(([key, value]) =>
122
- formatWhereClause(key, value)
158
+ formatWhereClause(key, value),
123
159
  );
124
160
  return `WHERE ${whereClauses.join(' AND ')}`;
125
161
  }
@@ -24,7 +24,7 @@ const model = {
24
24
  default: null,
25
25
  },
26
26
  {
27
- name: 'isUpdated',
27
+ name: 'is_updated',
28
28
  type: 'integer',
29
29
  required: true,
30
30
  min: 0,
@@ -47,7 +47,7 @@ const model = {
47
47
  default: '',
48
48
  },
49
49
  {
50
- name: 'isUpdated',
50
+ name: 'is_updated',
51
51
  type: 'integer',
52
52
  required: true,
53
53
  min: 0,
@@ -1,5 +1,5 @@
1
1
  const model = {
2
- filenameBase: 'stop_times_updates',
2
+ filenameBase: 'stop_time_updates',
3
3
  extension: 'gtfs-realtime',
4
4
  schema: [
5
5
  {
@@ -9,6 +9,18 @@ const model = {
9
9
  source: 'parent.tripUpdate.trip.tripId',
10
10
  default: null,
11
11
  },
12
+ {
13
+ name: 'trip_start_time',
14
+ type: 'varchar(255)',
15
+ source: 'parent.tripUpdate.trip.startTime',
16
+ default: null,
17
+ },
18
+ {
19
+ name: 'direction_id',
20
+ type: 'integer',
21
+ source: 'parent.tripUpdate.trip.directionId',
22
+ default: null,
23
+ },
12
24
  {
13
25
  name: 'route_id',
14
26
  type: 'varchar(255)',
@@ -54,7 +66,13 @@ const model = {
54
66
  default: null,
55
67
  },
56
68
  {
57
- name: 'isUpdated',
69
+ name: 'schedule_relationship',
70
+ type: 'varchar(255)',
71
+ source: 'scheduleRelationship',
72
+ default: null,
73
+ },
74
+ {
75
+ name: 'is_updated',
58
76
  type: 'integer',
59
77
  required: true,
60
78
  min: 0,
@@ -24,6 +24,25 @@ const model = {
24
24
  source: 'tripUpdate.trip.tripId',
25
25
  default: null,
26
26
  },
27
+ {
28
+ name: 'trip_start_time',
29
+ type: 'varchar(255)',
30
+ source: 'tripUpdate.trip.startTime',
31
+ default: null,
32
+ },
33
+ {
34
+ name: 'direction_id',
35
+ type: 'integer',
36
+ source: 'tripUpdate.trip.directionId',
37
+ default: null,
38
+ },
39
+ {
40
+ name: 'route_id',
41
+ type: 'varchar(255)',
42
+ index: true,
43
+ source: 'tripUpdate.trip.routeId',
44
+ default: null,
45
+ },
27
46
  {
28
47
  name: 'start_date',
29
48
  type: 'varchar(255)',
@@ -37,7 +56,13 @@ const model = {
37
56
  default: null,
38
57
  },
39
58
  {
40
- name: 'isUpdated',
59
+ name: 'schedule_relationship',
60
+ type: 'varchar(255)',
61
+ source: 'tripUpdate.trip.scheduleRelationship',
62
+ default: null,
63
+ },
64
+ {
65
+ name: 'is_updated',
41
66
  type: 'integer',
42
67
  required: true,
43
68
  min: 0,
@@ -60,7 +60,7 @@ const model = {
60
60
  default: null,
61
61
  },
62
62
  {
63
- name: 'isUpdated',
63
+ name: 'is_updated',
64
64
  type: 'integer',
65
65
  required: true,
66
66
  min: 0,
package/models/models.js CHANGED
@@ -40,7 +40,7 @@ import tripCapacity from '../models/gtfs-ride/trip-capacity.js';
40
40
  import rideFeedInfo from './gtfs-ride/ride-feed-info.js';
41
41
 
42
42
  import tripUpdates from './gtfs-realtime/trip-updates.js';
43
- import stopTimesUpdates from './gtfs-realtime/stop-times-updates.js';
43
+ import stopTimeUpdates from './gtfs-realtime/stop-time-updates.js';
44
44
  import vehiclePositions from './gtfs-realtime/vehicle-positions.js';
45
45
  import serviceAlerts from './gtfs-realtime/service-alerts.js';
46
46
  import serviceAlertTargets from './gtfs-realtime/service-alert-targets.js';
@@ -90,7 +90,7 @@ const models = [
90
90
  ridership,
91
91
  tripCapacity,
92
92
  tripUpdates,
93
- stopTimesUpdates,
93
+ stopTimeUpdates,
94
94
  vehiclePositions,
95
95
  serviceAlerts,
96
96
  serviceAlertTargets,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtfs",
3
- "version": "4.5.1",
3
+ "version": "4.7.0",
4
4
  "description": "Import GTFS transit data into SQLite and query routes, stops, times, fares and more",
5
5
  "keywords": [
6
6
  "transit",
@@ -58,7 +58,8 @@
58
58
  "Daniel Sörlöv",
59
59
  "Ali Zarghami <alizarghami@gmail.com>",
60
60
  "David Abell",
61
- "Matthias Feist <matze@matf.de>"
61
+ "Matthias Feist <matze@matf.de>",
62
+ "Oliv4945"
62
63
  ],
63
64
  "type": "module",
64
65
  "main": "index.js",
@@ -73,9 +74,9 @@
73
74
  },
74
75
  "dependencies": {
75
76
  "@turf/helpers": "^6.5.0",
76
- "better-sqlite3": "^9.1.1",
77
- "csv-parse": "^5.5.2",
78
- "csv-stringify": "^6.4.4",
77
+ "better-sqlite3": "^9.4.0",
78
+ "csv-parse": "^5.5.3",
79
+ "csv-stringify": "^6.4.5",
79
80
  "gtfs-realtime-bindings": "^1.1.1",
80
81
  "lodash-es": "^4.17.21",
81
82
  "long": "^5.2.3",
@@ -94,10 +95,10 @@
94
95
  "yoctocolors": "^1.0.0"
95
96
  },
96
97
  "devDependencies": {
97
- "husky": "^8.0.3",
98
- "lint-staged": "^15.0.2",
99
- "mocha": "^10.2.0",
100
- "prettier": "^3.0.3",
98
+ "husky": "^9.0.10",
99
+ "lint-staged": "^15.2.2",
100
+ "mocha": "^10.3.0",
101
+ "prettier": "^3.2.5",
101
102
  "should": "^13.2.3"
102
103
  },
103
104
  "engines": {
@@ -64,4 +64,24 @@ describe('getStopsAsGeoJSON(): ', () => {
64
64
  should.exist(geojson.features[0].geometry.coordinates);
65
65
  geojson.features[0].geometry.coordinates.length.should.equal(2);
66
66
  });
67
+
68
+ it('should return geojson with specific stops for bounding box query', () => {
69
+ const distance = 100;
70
+ const stopLatitude = 37.709538;
71
+ const stopLongitude = -122.401586;
72
+
73
+ const geojson = getStopsAsGeoJSON(
74
+ {
75
+ stop_lat: stopLatitude,
76
+ stop_lon: stopLongitude,
77
+ },
78
+ { bounding_box_side_m: distance },
79
+ );
80
+
81
+ should.exist(geojson);
82
+ geojson.type.should.equal('FeatureCollection');
83
+ geojson.features.length.should.equal(2);
84
+ should.exist(geojson.features[0].geometry.coordinates);
85
+ geojson.features[0].geometry.coordinates.length.should.equal(2);
86
+ });
67
87
  });
@@ -92,7 +92,7 @@ describe('getStops():', () => {
92
92
  route_id: routeId,
93
93
  },
94
94
  [],
95
- [['stop_id', 'ASC']]
95
+ [['stop_id', 'ASC']],
96
96
  );
97
97
 
98
98
  const expectedStopIds = [
@@ -127,7 +127,7 @@ describe('getStops():', () => {
127
127
  for (const [idx, stop] of results.entries()) {
128
128
  expectedStopIds[idx].should.equal(
129
129
  stop.stop_id,
130
- 'The order of stops are expected to be the same'
130
+ 'The order of stops are expected to be the same',
131
131
  );
132
132
  }
133
133
  });
@@ -142,7 +142,7 @@ describe('getStops():', () => {
142
142
  direction_id: directionId,
143
143
  },
144
144
  [],
145
- [['stop_id', 'ASC']]
145
+ [['stop_id', 'ASC']],
146
146
  );
147
147
 
148
148
  const expectedStopIds = [
@@ -165,7 +165,7 @@ describe('getStops():', () => {
165
165
  for (const [idx, stop] of results.entries()) {
166
166
  expectedStopIds[idx].should.equal(
167
167
  stop.stop_id,
168
- 'The order of stops are expected to be the same'
168
+ 'The order of stops are expected to be the same',
169
169
  );
170
170
  }
171
171
  });
@@ -178,7 +178,7 @@ describe('getStops():', () => {
178
178
  trip_id: tripId,
179
179
  },
180
180
  [],
181
- [['stop_id', 'ASC']]
181
+ [['stop_id', 'ASC']],
182
182
  );
183
183
 
184
184
  const expectedStopIds = [
@@ -213,7 +213,7 @@ describe('getStops():', () => {
213
213
  for (const [idx, stop] of results.entries()) {
214
214
  expectedStopIds[idx].should.equal(
215
215
  stop.stop_id,
216
- 'The order of stops are expected to be the same'
216
+ 'The order of stops are expected to be the same',
217
217
  );
218
218
  }
219
219
  });
@@ -226,7 +226,7 @@ describe('getStops():', () => {
226
226
  shape_id: shapeId,
227
227
  },
228
228
  [],
229
- [['stop_id', 'ASC']]
229
+ [['stop_id', 'ASC']],
230
230
  );
231
231
 
232
232
  const expectedStopIds = [
@@ -262,8 +262,82 @@ describe('getStops():', () => {
262
262
  for (const [idx, stop] of results.entries()) {
263
263
  expectedStopIds[idx].should.equal(
264
264
  stop.stop_id,
265
- 'The order of stops are expected to be the same'
265
+ 'The order of stops are expected to be the same',
266
266
  );
267
267
  }
268
268
  });
269
+
270
+ it('should return array of stops for bounding box query', () => {
271
+ const distance = 100;
272
+ const stopLatitude = 37.709538;
273
+ const stopLongitude = -122.401586;
274
+
275
+ const results = getStops(
276
+ {
277
+ stop_lat: stopLatitude,
278
+ stop_lon: stopLongitude,
279
+ },
280
+ [],
281
+ [],
282
+ { bounding_box_side_m: distance },
283
+ );
284
+
285
+ const expectedResult = [
286
+ {
287
+ stop_id: 'ctba',
288
+ stop_code: null,
289
+ stop_name: 'Bayshore Caltrain',
290
+ tts_stop_name: null,
291
+ stop_desc: null,
292
+ stop_lat: 37.709544,
293
+ stop_lon: -122.401318,
294
+ zone_id: null,
295
+ stop_url: 'http://www.caltrain.com/stations/bayshorestation.html',
296
+ location_type: 1,
297
+ parent_station: null,
298
+ stop_timezone: null,
299
+ wheelchair_boarding: 1,
300
+ level_id: null,
301
+ platform_code: null,
302
+ },
303
+ {
304
+ stop_id: '70032',
305
+ stop_code: '70032',
306
+ stop_name: 'Bayshore Caltrain',
307
+ tts_stop_name: null,
308
+ stop_desc: null,
309
+ stop_lat: 37.709544,
310
+ stop_lon: -122.40198,
311
+ zone_id: '1',
312
+ stop_url: 'http://www.caltrain.com/stations/bayshorestation.html',
313
+ location_type: 0,
314
+ parent_station: 'ctba',
315
+ stop_timezone: null,
316
+ wheelchair_boarding: 1,
317
+ level_id: null,
318
+ platform_code: 'SB',
319
+ },
320
+ {
321
+ stop_id: '70031',
322
+ stop_code: '70031',
323
+ stop_name: 'Bayshore Caltrain',
324
+ tts_stop_name: null,
325
+ stop_desc: null,
326
+ stop_lat: 37.709537,
327
+ stop_lon: -122.401586,
328
+ zone_id: '1',
329
+ stop_url: 'http://www.caltrain.com/stations/bayshorestation.html',
330
+ location_type: 0,
331
+ parent_station: 'ctba',
332
+ stop_timezone: null,
333
+ wheelchair_boarding: 1,
334
+ level_id: null,
335
+ platform_code: 'NB',
336
+ },
337
+ ];
338
+
339
+ should.exist(results);
340
+ results.length.should.equal(3);
341
+ results.should.match(expectedResult);
342
+ });
269
343
  });