@golemio/pid 2.1.13 → 2.1.14-dev.511689839

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.
Files changed (75) hide show
  1. package/db/example/{__truncate_tables.sql → 00_truncate_tables.sql} +0 -0
  2. package/db/example/{departure_directions.sql → 01_departure_directions.sql} +0 -0
  3. package/db/example/{pid_line_example_115.sql → 02_pid_line_example_115.sql} +0 -0
  4. package/db/example/{ropid_gtfs_dump.sql → 03_ropidgtfs_dump.sql} +27 -27
  5. package/db/example/{ropidvymi_example.sql → 04_ropidvymi_example.sql} +0 -0
  6. package/db/example/{vehicle_positions_dump.sql → 05_vehicle_positions_dump.sql} +0 -0
  7. package/db/example/06_ropidgtfs_train_trips_stop_times.sql +83 -0
  8. package/db/example/{tmp_example.sql → 07_tmp_example.sql} +0 -0
  9. package/db/example/{zzz_precomputed_tables.sql → 99_precomputed_tables.sql} +0 -0
  10. package/db/example/{trip_updates.pb → pb/trip_updates.pb} +0 -0
  11. package/db/example/{vehicle_positions.pb → pb/vehicle_positions.pb} +0 -0
  12. package/dist/const.d.ts +4 -0
  13. package/dist/const.js +6 -1
  14. package/dist/const.js.map +1 -1
  15. package/dist/integration-engine/ropid-gtfs/RopidGTFSScheduledTripsModel.d.ts +1 -1
  16. package/dist/integration-engine/ropid-gtfs/RopidGTFSStopTimesModel.d.ts +11 -0
  17. package/dist/integration-engine/ropid-gtfs/RopidGTFSStopTimesModel.js +34 -0
  18. package/dist/integration-engine/ropid-gtfs/RopidGTFSStopTimesModel.js.map +1 -1
  19. package/dist/integration-engine/ropid-gtfs/RopidGTFSTripsModel.js +2 -1
  20. package/dist/integration-engine/ropid-gtfs/RopidGTFSTripsModel.js.map +1 -1
  21. package/dist/integration-engine/ropid-gtfs/RopidGTFSWorker.js +2 -2
  22. package/dist/integration-engine/ropid-gtfs/RopidGTFSWorker.js.map +1 -1
  23. package/dist/integration-engine/vehicle-positions/VehiclePositionsInterfaces.d.ts +152 -0
  24. package/dist/integration-engine/vehicle-positions/VehiclePositionsInterfaces.js +3 -0
  25. package/dist/integration-engine/vehicle-positions/VehiclePositionsInterfaces.js.map +1 -0
  26. package/dist/integration-engine/vehicle-positions/VehiclePositionsUtils.d.ts +30 -0
  27. package/dist/integration-engine/vehicle-positions/VehiclePositionsUtils.js +58 -0
  28. package/dist/integration-engine/vehicle-positions/VehiclePositionsUtils.js.map +1 -0
  29. package/dist/integration-engine/vehicle-positions/VehiclePositionsWorker.d.ts +43 -90
  30. package/dist/integration-engine/vehicle-positions/VehiclePositionsWorker.js +366 -231
  31. package/dist/integration-engine/vehicle-positions/VehiclePositionsWorker.js.map +1 -1
  32. package/dist/integration-engine/vehicle-positions/index.d.ts +2 -5
  33. package/dist/integration-engine/vehicle-positions/index.js +2 -5
  34. package/dist/integration-engine/vehicle-positions/index.js.map +1 -1
  35. package/dist/integration-engine/vehicle-positions/{VehiclePositionsPositionsModel.d.ts → models/VehiclePositionsPositionsModel.d.ts} +3 -3
  36. package/dist/integration-engine/vehicle-positions/{VehiclePositionsPositionsModel.js → models/VehiclePositionsPositionsModel.js} +3 -3
  37. package/dist/integration-engine/vehicle-positions/models/VehiclePositionsPositionsModel.js.map +1 -0
  38. package/dist/integration-engine/vehicle-positions/{VehiclePositionsRunsMessagesModel.d.ts → models/VehiclePositionsRunsMessagesModel.d.ts} +0 -0
  39. package/dist/integration-engine/vehicle-positions/{VehiclePositionsRunsMessagesModel.js → models/VehiclePositionsRunsMessagesModel.js} +5 -5
  40. package/dist/integration-engine/vehicle-positions/models/VehiclePositionsRunsMessagesModel.js.map +1 -0
  41. package/dist/integration-engine/vehicle-positions/{VehiclePositionsRunsModel.d.ts → models/VehiclePositionsRunsModel.d.ts} +2 -2
  42. package/dist/integration-engine/vehicle-positions/{VehiclePositionsRunsModel.js → models/VehiclePositionsRunsModel.js} +2 -2
  43. package/dist/integration-engine/vehicle-positions/models/VehiclePositionsRunsModel.js.map +1 -0
  44. package/dist/integration-engine/vehicle-positions/{VehiclePositionsStopsModel.d.ts → models/VehiclePositionsStopsModel.d.ts} +0 -0
  45. package/dist/integration-engine/vehicle-positions/{VehiclePositionsStopsModel.js → models/VehiclePositionsStopsModel.js} +1 -1
  46. package/dist/integration-engine/vehicle-positions/models/VehiclePositionsStopsModel.js.map +1 -0
  47. package/dist/integration-engine/vehicle-positions/{VehiclePositionsTripsModel.d.ts → models/VehiclePositionsTripsModel.d.ts} +5 -7
  48. package/dist/integration-engine/vehicle-positions/{VehiclePositionsTripsModel.js → models/VehiclePositionsTripsModel.js} +54 -31
  49. package/dist/integration-engine/vehicle-positions/models/VehiclePositionsTripsModel.js.map +1 -0
  50. package/dist/integration-engine/vehicle-positions/models/index.d.ts +5 -0
  51. package/dist/integration-engine/vehicle-positions/models/index.js +18 -0
  52. package/dist/integration-engine/vehicle-positions/models/index.js.map +1 -0
  53. package/dist/integration-engine/vehicle-positions/{VehiclePositionsRunsTransformation.d.ts → transformations/VehiclePositionsRunsTransformation.d.ts} +1 -1
  54. package/dist/integration-engine/vehicle-positions/{VehiclePositionsRunsTransformation.js → transformations/VehiclePositionsRunsTransformation.js} +1 -1
  55. package/dist/integration-engine/vehicle-positions/transformations/VehiclePositionsRunsTransformation.js.map +1 -0
  56. package/dist/integration-engine/vehicle-positions/transformations/VehiclePositionsTransformation.d.ts +108 -0
  57. package/dist/integration-engine/vehicle-positions/{VehiclePositionsTransformation.js → transformations/VehiclePositionsTransformation.js} +42 -45
  58. package/dist/integration-engine/vehicle-positions/transformations/VehiclePositionsTransformation.js.map +1 -0
  59. package/dist/integration-engine/vehicle-positions/transformations/index.d.ts +2 -0
  60. package/dist/integration-engine/vehicle-positions/transformations/index.js +15 -0
  61. package/dist/integration-engine/vehicle-positions/transformations/index.js.map +1 -0
  62. package/dist/output-gateway/vehicle-positions/models/VehiclePositionsTripsModel.js +1 -1
  63. package/dist/output-gateway/vehicle-positions/models/VehiclePositionsTripsModel.js.map +1 -1
  64. package/dist/schema-definitions/vehicle-positions/index.d.ts +9 -2
  65. package/dist/schema-definitions/vehicle-positions/index.js +18 -2
  66. package/dist/schema-definitions/vehicle-positions/index.js.map +1 -1
  67. package/package.json +83 -82
  68. package/dist/integration-engine/vehicle-positions/VehiclePositionsPositionsModel.js.map +0 -1
  69. package/dist/integration-engine/vehicle-positions/VehiclePositionsRunsMessagesModel.js.map +0 -1
  70. package/dist/integration-engine/vehicle-positions/VehiclePositionsRunsModel.js.map +0 -1
  71. package/dist/integration-engine/vehicle-positions/VehiclePositionsRunsTransformation.js.map +0 -1
  72. package/dist/integration-engine/vehicle-positions/VehiclePositionsStopsModel.js.map +0 -1
  73. package/dist/integration-engine/vehicle-positions/VehiclePositionsTransformation.d.ts +0 -49
  74. package/dist/integration-engine/vehicle-positions/VehiclePositionsTransformation.js.map +0 -1
  75. package/dist/integration-engine/vehicle-positions/VehiclePositionsTripsModel.js.map +0 -1
@@ -27,13 +27,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
27
27
  step((generator = generator.apply(thisArg, _arguments || [])).next());
28
28
  });
29
29
  };
30
- var __asyncValues = (this && this.__asyncValues) || function (o) {
31
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
32
- var m = o[Symbol.asyncIterator], i;
33
- return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
34
- function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
35
- function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
36
- };
37
30
  var __importDefault = (this && this.__importDefault) || function (mod) {
38
31
  return (mod && mod.__esModule) ? mod : { "default": mod };
39
32
  };
@@ -52,12 +45,13 @@ const helpers_1 = require("@golemio/core/dist/integration-engine/helpers");
52
45
  const models_1 = require("@golemio/core/dist/integration-engine/models");
53
46
  const workers_1 = require("@golemio/core/dist/integration-engine/workers");
54
47
  const ropid_gtfs_1 = require("../../schema-definitions/ropid-gtfs");
55
- const RopidGTFSTripsModel_1 = require("../ropid-gtfs/RopidGTFSTripsModel");
56
48
  const vehicle_positions_1 = require("../../schema-definitions/vehicle-positions");
57
- const _1 = require("./");
58
49
  const const_1 = require("../../const");
59
- const VehiclePositionsRunsTransformation_1 = require("./VehiclePositionsRunsTransformation");
50
+ const RopidGTFSTripsModel_1 = require("../ropid-gtfs/RopidGTFSTripsModel");
60
51
  const RopidGTFSStopTimesModel_1 = require("../ropid-gtfs/RopidGTFSStopTimesModel");
52
+ const models_2 = require("./models");
53
+ const transformations_1 = require("./transformations");
54
+ const VehiclePositionsUtils_1 = require("./VehiclePositionsUtils");
61
55
  var PositionHandlerEnum;
62
56
  (function (PositionHandlerEnum) {
63
57
  PositionHandlerEnum["TRACKING"] = "TRACKING";
@@ -109,10 +103,11 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
109
103
  }
110
104
  }
111
105
  for (const key in chunks) {
112
- const allIds = yield this.modelTrips.findAllAssocTripIds(chunks[key]);
106
+ const positions = transformedData.positions.filter((position) => chunks[key].includes(position.trips_id));
107
+ const updatedTrips = yield this.modelTrips.findAllAssocTripIds(chunks[key]);
113
108
  yield this.sendMessageToExchange("workers." + this.queuePrefix + ".updateDelay", JSON.stringify({
114
- positions: transformedData.positions.filter((position) => chunks[key].includes(position.trips_id)),
115
- updatedTrips: allIds,
109
+ positions,
110
+ updatedTrips,
116
111
  }));
117
112
  }
118
113
  });
@@ -131,7 +126,7 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
131
126
  this.updateGTFSTripId = (msg) => __awaiter(this, void 0, void 0, function* () {
132
127
  const inputData = JSON.parse(msg.content.toString());
133
128
  // unique ids in input data in key-value (id-positions) structure
134
- const positionsByIds = {};
129
+ let positionsByIds = {};
135
130
  for (const position of inputData.positions) {
136
131
  positionsByIds[position.trips_id] = position;
137
132
  }
@@ -140,13 +135,13 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
140
135
  if (!trip.origin_route_name || !trip.sequence_id || !positionsByIds[trip.id].origin_timestamp)
141
136
  return;
142
137
  const timestampLast = (0, moment_timezone_1.default)(positionsByIds[trip.id].origin_timestamp).tz("Europe/Prague").format();
143
- return this.getScheduledTrips({
138
+ return this.setAndReturnScheduledTrips({
144
139
  route_id: trip.origin_route_name,
145
140
  run_number: trip.sequence_id,
146
141
  msg_last_timestamp: timestampLast,
147
142
  });
148
143
  }));
149
- // find for each trip gtfs data
144
+ // find gtfs data for each trip
150
145
  const promiseValues = yield Promise.all(inputData.trips.map((trip) => __awaiter(this, void 0, void 0, function* () {
151
146
  try {
152
147
  return yield this.modelTrips.findGTFSTripId(trip);
@@ -156,35 +151,76 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
156
151
  return err;
157
152
  }
158
153
  })));
154
+ let existingTripsPromiseValues = [], existingTripsPromiseErrors = [];
159
155
  // filter only trips with found gtfs data
160
- const foundedTripsPromiseValues = [].concat(...promiseValues.filter((result) => !(result instanceof golemio_errors_1.CustomError)));
161
- const foundedTripsPromiseErrors = promiseValues.filter((result) => result instanceof golemio_errors_1.CustomError);
156
+ for (const trip of promiseValues) {
157
+ if (trip instanceof golemio_errors_1.CustomError) {
158
+ existingTripsPromiseErrors.push(trip);
159
+ }
160
+ else if (trip instanceof Array) {
161
+ existingTripsPromiseValues.push(...trip);
162
+ }
163
+ else if ((trip === null || trip === void 0 ? void 0 : trip.constructor) === Object) {
164
+ existingTripsPromiseValues.push(trip);
165
+ }
166
+ }
167
+ let tripsByBlockId = {};
168
+ for (const trip of existingTripsPromiseValues) {
169
+ if (!trip.gtfs_block_id || !trip.gtfs_trip_id)
170
+ continue;
171
+ if (!tripsByBlockId[trip.gtfs_block_id]) {
172
+ tripsByBlockId[trip.gtfs_block_id] = new Set();
173
+ }
174
+ tripsByBlockId[trip.gtfs_block_id].add(trip.gtfs_trip_id);
175
+ }
176
+ // create cache for trip stops (grouped by block id)
177
+ const tripStopsSchedulesByBlockIds = yield Object.entries(tripsByBlockId).reduce((acc, [blockId, gtfsTripIds]) => __awaiter(this, void 0, void 0, function* () {
178
+ const tripStops = yield acc;
179
+ try {
180
+ tripStops[blockId] = yield this.setAndReturnTripStopsSchedule(blockId, [...gtfsTripIds]);
181
+ }
182
+ catch (err) {
183
+ helpers_1.IntegrationErrorHandler.handle(err);
184
+ }
185
+ return acc;
186
+ }), Promise.resolve({}));
162
187
  let currentPosition;
163
- for (const trip of foundedTripsPromiseValues) {
188
+ for (const trip of existingTripsPromiseValues) {
164
189
  if (positionsByIds[trip.id]) {
165
190
  currentPosition = positionsByIds[trip.id];
191
+ if (trip.gtfs_block_id) {
192
+ // Determine current position tracking status
193
+ currentPosition.tracking = VehiclePositionsUtils_1.VehiclePositionsUtils.determineTrackingStatus(trip.gtfs_trip_id, tripStopsSchedulesByBlockIds[trip.gtfs_block_id], currentPosition);
194
+ }
195
+ continue;
166
196
  }
167
- else {
168
- // duplicate position with new created trip for second train trip
197
+ if (!currentPosition) {
198
+ continue;
199
+ }
200
+ // Determine position duplication and new position tracking status
201
+ const { shouldCreateNewPosition, trackingStatus: newPositionTrackingStatus } = VehiclePositionsUtils_1.VehiclePositionsUtils.determineNewPositionTracking(tripStopsSchedulesByBlockIds[trip.gtfs_block_id], trip, currentPosition);
202
+ if (shouldCreateNewPosition) {
203
+ // duplicate position with new created trip for second trip
169
204
  const newPosition = Object.assign({}, currentPosition);
170
205
  newPosition.trips_id = trip.id;
206
+ newPosition.tracking = newPositionTrackingStatus;
171
207
  inputData.positions.push(newPosition);
172
208
  }
173
209
  }
174
210
  yield this.modelPositions.save(inputData.positions);
175
211
  // successfully updated gtfs ids
176
- for (let i = 0, chunkSize = 100, imax = foundedTripsPromiseValues.length; i < imax; i += chunkSize) {
212
+ for (let i = 0, chunkSize = 100, imax = existingTripsPromiseValues.length; i < imax; i += chunkSize) {
177
213
  yield this.sendMessageToExchange("workers." + this.queuePrefix + ".updateDelay", JSON.stringify({
178
214
  positions: [],
179
- updatedTrips: foundedTripsPromiseValues.slice(i, i + chunkSize),
215
+ updatedTrips: existingTripsPromiseValues.slice(i, i + chunkSize),
180
216
  }));
181
217
  }
182
218
  // gtfsId updating errors
183
- for (let i = 0, imax = foundedTripsPromiseErrors.length; i < imax; i++) {
184
- helpers_1.IntegrationErrorHandler.handle(foundedTripsPromiseErrors[i]);
219
+ for (let i = 0, imax = existingTripsPromiseErrors.length; i < imax; i++) {
220
+ helpers_1.IntegrationErrorHandler.handle(existingTripsPromiseErrors[i]);
185
221
  }
186
222
  });
187
- this.generateGtfsRt = (msg) => __awaiter(this, void 0, void 0, function* () {
223
+ this.generateGtfsRt = () => __awaiter(this, void 0, void 0, function* () {
188
224
  const results = yield this.modelTrips.findAllForGTFSRt();
189
225
  const updatesMessage = gtfsRealtime.FeedMessage.create();
190
226
  const positionsMessage = gtfsRealtime.FeedMessage.create();
@@ -205,7 +241,7 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
205
241
  tripId: r.gtfs_trip_id,
206
242
  routeId: r.gtfs_route_id,
207
243
  };
208
- const vehicleDescriptor = Object.assign({ id: r.gtfs_route_type === _1.GTFSRouteTypeEnum.TRAIN
244
+ const vehicleDescriptor = Object.assign({ id: r.gtfs_route_type === models_2.GTFSRouteTypeEnum.TRAIN
209
245
  ? "train-" + r.cis_trip_number
210
246
  : "service-" + r.gtfs_route_type + "-" + r.vehicle_registration_number }, (r.vehicle_registration_number && {
211
247
  label: r.vehicle_registration_number,
@@ -334,32 +370,67 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
334
370
  const tripIds = inputData.updatedTrips.map((trip) => trip.id);
335
371
  if (((_b = inputData.positions) === null || _b === void 0 ? void 0 : _b.length) > 0) {
336
372
  // unique ids in input data in key-value (id-positions) structure
337
- const positionsByIds = inputData.positions.reduce((acc, position) => {
338
- acc[position.trips_id] = position;
339
- return acc;
340
- }, {});
341
- // unique block_id's and its parent input trips_id in key-value (block_id-trips_id) structure
342
- const blockIdsToParentTripsId = inputData.updatedTrips.reduce((acc, currentTrip) => {
343
- if (!acc[currentTrip.gtfs_block_id]) {
344
- // init key value pair if not exists
345
- acc[currentTrip.gtfs_block_id] = currentTrip.id;
373
+ let positionsByIds = {};
374
+ for (const position of inputData.positions) {
375
+ positionsByIds[position.trips_id] = position;
376
+ }
377
+ // unique block_ids and its parent input trips_id in key-value structure
378
+ let tripsIdsByBlockIds = {};
379
+ // unique block_ids and its parent gtfs_trip_id in key-value structure
380
+ let gtfsTripIdsByBlockIds = {};
381
+ for (const trip of inputData.updatedTrips) {
382
+ if (!trip.gtfs_block_id) {
383
+ continue;
384
+ }
385
+ if (!tripsIdsByBlockIds[trip.gtfs_block_id]) {
386
+ tripsIdsByBlockIds[trip.gtfs_block_id] = trip.id;
346
387
  }
347
388
  else {
348
- // otherwise save only the shorter trip_id (it is the raw input id with no suffixes)
349
- acc[currentTrip.gtfs_block_id] =
350
- acc[currentTrip.gtfs_block_id] > currentTrip.id
351
- ? currentTrip.id
352
- : acc[currentTrip.gtfs_block_id];
389
+ // save only the shorter trip_id (it is the raw input id with no suffixes)
390
+ tripsIdsByBlockIds[trip.gtfs_block_id] =
391
+ tripsIdsByBlockIds[trip.gtfs_block_id] > trip.id ? trip.id : tripsIdsByBlockIds[trip.gtfs_block_id];
392
+ }
393
+ if (!gtfsTripIdsByBlockIds[trip.gtfs_block_id]) {
394
+ gtfsTripIdsByBlockIds[trip.gtfs_block_id] = new Set();
395
+ }
396
+ if (trip.gtfs_trip_id) {
397
+ gtfsTripIdsByBlockIds[trip.gtfs_block_id].add(trip.gtfs_trip_id);
398
+ }
399
+ }
400
+ // Get cached schedule for each unique block_id
401
+ const tripStopsSchedulesByBlockIds = yield Object.entries(gtfsTripIdsByBlockIds).reduce((acc, [blockId, gtfsTripIds]) => __awaiter(this, void 0, void 0, function* () {
402
+ const tripStops = yield acc;
403
+ try {
404
+ const schedule = yield this.setAndReturnTripStopsSchedule(blockId, [...gtfsTripIds]);
405
+ tripStops[blockId] = schedule;
406
+ }
407
+ catch (err) {
408
+ helpers_1.IntegrationErrorHandler.handle(err);
353
409
  }
354
410
  return acc;
355
- }, {});
411
+ }), Promise.resolve({}));
356
412
  // duplicate positions for trips with block_ids
357
413
  for (const trip of inputData.updatedTrips) {
358
- if (!positionsByIds[trip.id]) {
359
- const newPosition = Object.assign({}, inputData.positions.find((position) => {
360
- return position.trips_id === blockIdsToParentTripsId[trip.gtfs_block_id];
361
- }));
414
+ if (positionsByIds[trip.id]) {
415
+ if (trip.gtfs_block_id) {
416
+ // Determine current position tracking status
417
+ positionsByIds[trip.id].tracking = VehiclePositionsUtils_1.VehiclePositionsUtils.determineTrackingStatus(trip.gtfs_trip_id, tripStopsSchedulesByBlockIds[trip.gtfs_block_id], positionsByIds[trip.id]);
418
+ }
419
+ continue;
420
+ }
421
+ const currentPosition = inputData.positions.find((position) => {
422
+ return position.trips_id === tripsIdsByBlockIds[trip.gtfs_block_id];
423
+ });
424
+ if (!currentPosition) {
425
+ continue;
426
+ }
427
+ // Determine position duplication and new position tracking status
428
+ const { shouldCreateNewPosition, trackingStatus: newPositionTrackingStatus } = VehiclePositionsUtils_1.VehiclePositionsUtils.determineNewPositionTracking(tripStopsSchedulesByBlockIds[trip.gtfs_block_id], trip, currentPosition);
429
+ if (shouldCreateNewPosition) {
430
+ // duplicate position with new created trip for second trip
431
+ const newPosition = Object.assign({}, currentPosition);
362
432
  newPosition.trips_id = trip.id;
433
+ newPosition.tracking = newPositionTrackingStatus;
363
434
  inputData.positions.push(newPosition);
364
435
  }
365
436
  }
@@ -382,7 +453,7 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
382
453
  helpers_1.log.debug("Delay Computation data (Redis) was not found. (gtfsTripId = " + trip.gtfs_trip_id + ")");
383
454
  try {
384
455
  const gtfs = yield this.getResultObjectForDelayCalculation(trip.gtfs_trip_id);
385
- yield this.delayComputationTripsModel.set("trip_id", gtfs, 7200);
456
+ yield this.delayComputationTripsModel.set("trip_id", gtfs, VehiclePositionsUtils_1.VehiclePositionsUtils.DELAY_COMPUTATION_CACHE_TTL);
386
457
  trip.gtfsData = gtfs;
387
458
  }
388
459
  catch (err) {
@@ -399,7 +470,7 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
399
470
  }));
400
471
  }
401
472
  catch (err) {
402
- throw new golemio_errors_1.CustomError(`Error while updating delay.`, true, this.constructor.name, 5001, err);
473
+ throw new golemio_errors_1.CustomError(`Error while updating delay. For trip ids:${tripIds.slice(0, 10).join(",")}`, true, this.constructor.name, 5001, err);
403
474
  }
404
475
  }
405
476
  });
@@ -456,9 +527,8 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
456
527
  * @returns {void}
457
528
  */
458
529
  this.updateRunsGTFSTripId = (msg) => __awaiter(this, void 0, void 0, function* () {
459
- var e_1, _e;
460
530
  const input = JSON.parse(msg.content.toString());
461
- const schedule = yield this.getScheduledTrips(input.run);
531
+ const schedule = yield this.setAndReturnScheduledTrips(input.run);
462
532
  const { actual_stop_timestamp_scheduled: actualStopTimestampScheduled, msg_timestamp: msgTimestamp, events: event, } = input.run_message;
463
533
  const delayMsg = {
464
534
  updatedTrips: [],
@@ -473,62 +543,51 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
473
543
  }
474
544
  return;
475
545
  }
476
- if (actualStopTimestampScheduled) {
477
- const asts = (0, moment_timezone_1.default)(+actualStopTimestampScheduled);
478
- // current gtfs trip for run
479
- const gtfsCurrentTrip = schedule.find((el, i) => {
480
- return asts.isBetween((0, moment_timezone_1.default)(el.start_timestamp), (0, moment_timezone_1.default)(el.end_timestamp), "second", "[]");
481
- });
482
- let isCurrentTripTracking = false;
483
- if (gtfsCurrentTrip &&
484
- // tram is always on track while pushes messages only on stops
485
- (gtfsCurrentTrip.route_type === _1.GTFSRouteTypeEnum.TRAM ||
486
- // when bus announces DEPARTURE TCP event, it means that trip is on track
487
- (gtfsCurrentTrip.route_type === _1.GTFSRouteTypeEnum.BUS && event === const_1.TCPEventEnum.DEPARTURED) ||
488
- // when trip has set first stop time, and message timestamp is after scheduled start, set as tracking
489
- (asts.isSame((0, moment_timezone_1.default)(gtfsCurrentTrip.start_timestamp)) &&
490
- (0, moment_timezone_1.default)(msgTimestamp).isSameOrAfter((0, moment_timezone_1.default)(gtfsCurrentTrip.start_timestamp))) ||
491
- // of course when trip announcing some next stop time in schedule, then set as tracking
492
- asts.isAfter((0, moment_timezone_1.default)(gtfsCurrentTrip.start_timestamp)))) {
493
- isCurrentTripTracking = true;
494
- }
495
- const gtfsCurrentTripId = gtfsCurrentTrip && gtfsCurrentTrip.trip_id;
496
- if (!gtfsCurrentTripId) {
497
- helpers_1.log.warn(`updateRunsGTFSTripId: could not find current gtfsTrip for run: ${JSON.stringify(input)}`);
498
- }
499
- else {
500
- const currentTrip = yield this.modelTrips.upsertRunTrip(input, gtfsCurrentTrip);
501
- yield this.modelPositions.upsertRunPosition(input, gtfsCurrentTrip, isCurrentTripTracking);
502
- delayMsg.updatedTrips.push(currentTrip);
503
- }
504
- // last/next gtfs trip for run
505
- const gtfsLastNextTrips = schedule.filter((el) => {
506
- return (((asts.clone().subtract(15, "minutes") <= (0, moment_timezone_1.default)(el.end_timestamp) && (0, moment_timezone_1.default)(el.end_timestamp) <= asts) ||
507
- (asts <= (0, moment_timezone_1.default)(el.start_timestamp) && (0, moment_timezone_1.default)(el.start_timestamp) <= asts.clone().add(60, "minutes"))) &&
508
- el.trip_id !== (gtfsCurrentTrip === null || gtfsCurrentTrip === void 0 ? void 0 : gtfsCurrentTrip.trip_id));
509
- });
510
- try {
511
- for (var gtfsLastNextTrips_1 = __asyncValues(gtfsLastNextTrips), gtfsLastNextTrips_1_1; gtfsLastNextTrips_1_1 = yield gtfsLastNextTrips_1.next(), !gtfsLastNextTrips_1_1.done;) {
512
- const gtfsTrip = gtfsLastNextTrips_1_1.value;
513
- const trip = yield this.modelTrips.upsertRunTrip(input, gtfsTrip);
514
- yield this.modelPositions.upsertRunPosition(input, gtfsTrip, false);
515
- delayMsg.updatedTrips.push(trip);
516
- }
517
- }
518
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
519
- finally {
520
- try {
521
- if (gtfsLastNextTrips_1_1 && !gtfsLastNextTrips_1_1.done && (_e = gtfsLastNextTrips_1.return)) yield _e.call(gtfsLastNextTrips_1);
522
- }
523
- finally { if (e_1) throw e_1.error; }
524
- }
525
- // send to updateDelay only if there are some trips updated
526
- if (delayMsg.updatedTrips.length > 0) {
527
- yield this.sendMessageToExchange("workers." + this.queuePrefix + ".updateDelay", JSON.stringify(delayMsg));
528
- }
546
+ if (!actualStopTimestampScheduled) {
547
+ helpers_1.log.warn(`updateRunsGTFSTripId: actualStopTimestampScheduled is not provided for current run: ${JSON.stringify(input.run)}`);
548
+ return;
549
+ }
550
+ const asts = (0, moment_timezone_1.default)(+actualStopTimestampScheduled);
551
+ // current gtfs trip for run
552
+ const gtfsCurrentTrip = schedule.find((el, i) => {
553
+ return asts.isBetween((0, moment_timezone_1.default)(el.start_timestamp), (0, moment_timezone_1.default)(el.end_timestamp), "second", "[]");
554
+ });
555
+ let isCurrentTripTracking = false;
556
+ if (gtfsCurrentTrip &&
557
+ // tram is always on track while pushes messages only on stops
558
+ (gtfsCurrentTrip.route_type === models_2.GTFSRouteTypeEnum.TRAM ||
559
+ // when bus announces DEPARTURE TCP event, it means that trip is on track
560
+ (gtfsCurrentTrip.route_type === models_2.GTFSRouteTypeEnum.BUS && event === const_1.TCPEventEnum.DEPARTURED) ||
561
+ // when trip has set first stop time, and message timestamp is after scheduled start, set as tracking
562
+ (asts.isSame((0, moment_timezone_1.default)(gtfsCurrentTrip.start_timestamp)) &&
563
+ (0, moment_timezone_1.default)(msgTimestamp).isSameOrAfter((0, moment_timezone_1.default)(gtfsCurrentTrip.start_timestamp))) ||
564
+ // of course when trip announcing some next stop time in schedule, then set as tracking
565
+ asts.isAfter((0, moment_timezone_1.default)(gtfsCurrentTrip.start_timestamp)))) {
566
+ isCurrentTripTracking = true;
567
+ }
568
+ const gtfsCurrentTripId = gtfsCurrentTrip && gtfsCurrentTrip.trip_id;
569
+ if (!gtfsCurrentTripId) {
570
+ helpers_1.log.warn(`updateRunsGTFSTripId: could not find current gtfsTrip for run: ${JSON.stringify(input)}`);
529
571
  }
530
572
  else {
531
- helpers_1.log.warn(`updateRunsGTFSTripId: actualStopTimestampScheduled is not provided for current run: ${JSON.stringify(input.run)}`);
573
+ const currentTrip = yield this.modelTrips.upsertRunTrip(input, gtfsCurrentTrip);
574
+ yield this.modelPositions.upsertRunPosition(input, gtfsCurrentTrip, isCurrentTripTracking);
575
+ delayMsg.updatedTrips.push(currentTrip);
576
+ }
577
+ // last/next gtfs trip for run
578
+ const gtfsLastNextTrips = schedule.filter((el) => {
579
+ return (((asts.clone().subtract(15, "minutes") <= (0, moment_timezone_1.default)(el.end_timestamp) && (0, moment_timezone_1.default)(el.end_timestamp) <= asts) ||
580
+ (asts <= (0, moment_timezone_1.default)(el.start_timestamp) && (0, moment_timezone_1.default)(el.start_timestamp) <= asts.clone().add(60, "minutes"))) &&
581
+ el.trip_id !== (gtfsCurrentTrip === null || gtfsCurrentTrip === void 0 ? void 0 : gtfsCurrentTrip.trip_id));
582
+ });
583
+ for (const gtfsTrip of gtfsLastNextTrips) {
584
+ const trip = yield this.modelTrips.upsertRunTrip(input, gtfsTrip);
585
+ yield this.modelPositions.upsertRunPosition(input, gtfsTrip, false);
586
+ delayMsg.updatedTrips.push(trip);
587
+ }
588
+ // send to updateDelay only if there are some trips updated
589
+ if (delayMsg.updatedTrips.length > 0) {
590
+ yield this.sendMessageToExchange("workers." + this.queuePrefix + ".updateDelay", JSON.stringify(delayMsg));
532
591
  }
533
592
  });
534
593
  /**
@@ -558,7 +617,7 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
558
617
  *
559
618
  * @returns {string[]} array of tripIds
560
619
  */
561
- this.getScheduledTrips = (scheduleData) => __awaiter(this, void 0, void 0, function* () {
620
+ this.setAndReturnScheduledTrips = (scheduleData) => __awaiter(this, void 0, void 0, function* () {
562
621
  const { route_id, run_number, msg_last_timestamp } = scheduleData;
563
622
  const scheduledTripIds = yield this.runTripsModel.get(`${route_id}_${run_number}`);
564
623
  if (!(scheduledTripIds === null || scheduledTripIds === void 0 ? void 0 : scheduledTripIds.schedule)) {
@@ -568,11 +627,36 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
568
627
  el.start_timestamp = (0, moment_timezone_1.default)(el.start_timestamp).tz("Europe/Prague").format();
569
628
  el.end_timestamp = (0, moment_timezone_1.default)(el.end_timestamp).tz("Europe/Prague").format();
570
629
  });
571
- yield this.runTripsModel.set(`${route_id}_${run_number}`, { schedule: result }, 21600);
630
+ yield this.runTripsModel.set(`${route_id}_${run_number}`, { schedule: result }, VehiclePositionsUtils_1.VehiclePositionsUtils.RUN_TRIPS_CACHE_TTL);
572
631
  return result;
573
632
  }
574
633
  return scheduledTripIds.schedule;
575
634
  });
635
+ /**
636
+ * Get trip stops schedule by block id
637
+ *
638
+ * @param {string} blockId - Target gtfs block id
639
+ * @param {string} tripIds - List of inserted gtfs trip ids
640
+ */
641
+ this.setAndReturnTripStopsSchedule = (blockId, tripIds) => __awaiter(this, void 0, void 0, function* () {
642
+ const schedule = yield this.tripStopsModel.get(blockId);
643
+ if (schedule) {
644
+ return schedule;
645
+ }
646
+ try {
647
+ const tripStops = yield this.modelGTFSStopTimes.findTripStops(tripIds);
648
+ const tripStopsSchedule = tripStops.reduce((acc, curr) => {
649
+ acc[curr.trip_id] = { trip_stops: curr.trip_stops };
650
+ return acc;
651
+ }, {});
652
+ yield this.tripStopsModel.set(blockId, tripStopsSchedule, VehiclePositionsUtils_1.VehiclePositionsUtils.TRIP_STOPS_CACHE_TTL);
653
+ return tripStopsSchedule;
654
+ }
655
+ catch (err) {
656
+ helpers_1.log.warn("Unable to retrieve trip stops", err);
657
+ return {};
658
+ }
659
+ });
576
660
  /**
577
661
  * Takes all newly computed positions and saves it, then save the last ones to the trips
578
662
  *
@@ -680,9 +764,9 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
680
764
  return PositionHandlerEnum.DO_NOTHING;
681
765
  if (position.is_canceled)
682
766
  return PositionHandlerEnum.CANCELED;
683
- if (position.lat && position.lng && position.tracking === 2)
767
+ if (position.lat && position.lng && position.tracking === const_1.PositionTrackingEnum.TRACKING)
684
768
  return PositionHandlerEnum.TRACKING;
685
- if (position.tracking !== 2)
769
+ if (position.tracking !== const_1.PositionTrackingEnum.TRACKING)
686
770
  return PositionHandlerEnum.NOT_TRACKING;
687
771
  return PositionHandlerEnum.NOT_TRACKING;
688
772
  };
@@ -732,7 +816,7 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
732
816
  }
733
817
  else {
734
818
  // if there is tracking 2 position with same origin_timestamp then this position is duplicate
735
- const statePosition = tripPositions.positions.find((e) => e.origin_timestamp === position.origin_timestamp && e.tracking === 2)
819
+ const statePosition = tripPositions.positions.find((e) => e.origin_timestamp === position.origin_timestamp && e.tracking === const_1.PositionTrackingEnum.TRACKING)
736
820
  ? const_1.StatePositionEnum.DUPLICATE
737
821
  : const_1.StatePositionEnum.AFTER_TRACK;
738
822
  const lastShapesAnchorPoint = tripPositions.gtfsData.shapes_anchor_points[tripPositions.gtfsData.shapes_anchor_points.length - 1];
@@ -825,7 +909,6 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
825
909
  .valueOf();
826
910
  };
827
911
  // FIND ALL SHAPE POINTS IN POLYGON CIRCLE, GET SEGMENTS WITH CONSEQUENTING POINTS
828
- // TODO this should a class method
829
912
  this.getShapePointsAroundPosition = (tripShapePoints, radius) => __awaiter(this, void 0, void 0, function* () {
830
913
  const ptsInRadius = [];
831
914
  let segmentIndex = 0;
@@ -983,9 +1066,9 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
983
1066
  if (Math.abs(timeRealDiff) < Math.abs(minTimeRealDiff)) {
984
1067
  minTimeRealDiff = timeRealDiff;
985
1068
  // push trip into nearest at_stop shape anchor point if trip "P" event (tram) / "V" event (bus)
986
- if (((gtfsRouteType === _1.GTFSRouteTypeEnum.TRAM &&
1069
+ if (((gtfsRouteType === models_2.GTFSRouteTypeEnum.TRAM &&
987
1070
  currentPosition.properties.tcp_event === const_1.TCPEventEnum.ARRIVAL_ANNOUNCED) ||
988
- (gtfsRouteType === _1.GTFSRouteTypeEnum.BUS && currentPosition.properties.tcp_event === const_1.TCPEventEnum.V)) &&
1071
+ (gtfsRouteType === models_2.GTFSRouteTypeEnum.BUS && currentPosition.properties.tcp_event === const_1.TCPEventEnum.V)) &&
989
1072
  thisClosestPoint.this_stop_sequence === null) {
990
1073
  let movingShapePointIndex = thisClosestPoint.index;
991
1074
  while (thisClosestPoint.this_stop_sequence === null && movingShapePointIndex < lastTripShapePoint.index) {
@@ -1000,7 +1083,7 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
1000
1083
  statePosition = const_1.StatePositionEnum.ON_TRACK;
1001
1084
  }
1002
1085
  // if tram position has "V" avent tram is after track
1003
- if (gtfsRouteType === _1.GTFSRouteTypeEnum.TRAM && currentPosition.properties.tcp_event === const_1.TCPEventEnum.V) {
1086
+ if (gtfsRouteType === models_2.GTFSRouteTypeEnum.TRAM && currentPosition.properties.tcp_event === const_1.TCPEventEnum.V) {
1004
1087
  statePosition = const_1.StatePositionEnum.AFTER_TRACK;
1005
1088
  thisClosestPoint = lastTripShapePoint;
1006
1089
  timeDelay = null;
@@ -1062,17 +1145,17 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
1062
1145
  * @returns {IPositionToUpdate} - Position object to update
1063
1146
  */
1064
1147
  this.getEstimatedPoint = (tripGtfsData, currentPosition, context, startDayTimestamp, gtfsRouteType) => __awaiter(this, void 0, void 0, function* () {
1065
- var _f, _g, _h, _j, _k, _l;
1148
+ var _e, _f, _g, _h, _j, _k;
1066
1149
  // init radius around GPS position ( 200 meters radius, 16 points polygon aka circle)
1067
1150
  const radius = turf.circle(currentPosition, 0.2, { steps: 16 });
1068
1151
  //Initial value
1069
1152
  let estimatedPoint = Object.assign({ id: currentPosition.properties.id, state_position: const_1.StatePositionEnum.OFF_TRACK, state_process: const_1.StateProcessEnum.PROCESSED, tcp_event: currentPosition.properties.tcp_event }, (context &&
1070
- ((_f = context.lastPositionTracking) === null || _f === void 0 ? void 0 : _f.properties.last_stop_sequence) && {
1071
- shape_dist_traveled: (_g = context.lastPositionTracking) === null || _g === void 0 ? void 0 : _g.properties.shape_dist_traveled,
1072
- last_stop_arrival_time: (_h = context.lastPositionTracking) === null || _h === void 0 ? void 0 : _h.properties.last_stop_arrival_time,
1073
- last_stop_departure_time: (_j = context.lastPositionTracking) === null || _j === void 0 ? void 0 : _j.properties.last_stop_departure_time,
1074
- last_stop_sequence: (_k = context.lastPositionTracking) === null || _k === void 0 ? void 0 : _k.properties.last_stop_sequence,
1075
- last_stop_id: (_l = context.lastPositionTracking) === null || _l === void 0 ? void 0 : _l.properties.last_stop_id,
1153
+ ((_e = context.lastPositionTracking) === null || _e === void 0 ? void 0 : _e.properties.last_stop_sequence) && {
1154
+ shape_dist_traveled: (_f = context.lastPositionTracking) === null || _f === void 0 ? void 0 : _f.properties.shape_dist_traveled,
1155
+ last_stop_arrival_time: (_g = context.lastPositionTracking) === null || _g === void 0 ? void 0 : _g.properties.last_stop_arrival_time,
1156
+ last_stop_departure_time: (_h = context.lastPositionTracking) === null || _h === void 0 ? void 0 : _h.properties.last_stop_departure_time,
1157
+ last_stop_sequence: (_j = context.lastPositionTracking) === null || _j === void 0 ? void 0 : _j.properties.last_stop_sequence,
1158
+ last_stop_id: (_k = context.lastPositionTracking) === null || _k === void 0 ? void 0 : _k.properties.last_stop_id,
1076
1159
  }));
1077
1160
  // returns array of segments (row of points) of shape anchor points around gps
1078
1161
  const ptsInRadius = yield this.getShapePointsAroundPosition(tripGtfsData.shapes_anchor_points, radius);
@@ -1103,59 +1186,13 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
1103
1186
  if (!stopTimes || !stopTimes.stop_times || stopTimes.stop_times.length === 0) {
1104
1187
  throw new Error(`"trip.stop_times" was not found for id ${tripId}.`);
1105
1188
  }
1106
- // { <tripData>, stop_times: [ <stopTimesDataWithStop> ], shapes: [ <shapesData> ] }
1107
- const shapesGeometryAnchorPoints = yield this.getShapesGeometryAnchorPoints(stopTimes.shape_id, shapes.shapes);
1108
- // ----------------------------------------------
1109
- // TEMP constS
1110
- const stopSequenceMaximum = stopTimes.stop_times.length;
1189
+ const shapesGeometryAnchorPoints = this.getShapesGeometryAnchorPoints(stopTimes.shape_id, shapes.shapes, stopTimes.stop_times);
1111
1190
  // MAKING COPY
1112
1191
  const shapePoints = [];
1113
1192
  // const to determine shape points of stops (in meters)
1114
- const distanceBefore = 100;
1115
- const distanceAfter = 50;
1116
- // TODO this should a class method
1117
- const shapesAnchorPointsIterator = (i, cb) => {
1118
- // end of iteration
1119
- if (shapesGeometryAnchorPoints.length === i) {
1120
- return cb();
1121
- }
1122
- // find stop in near distance
1123
- const stopNearby = stopTimes.stop_times.reduce((r, s, j) => {
1124
- if (shapesGeometryAnchorPoints[i].shape_dist_traveled >=
1125
- s.shape_dist_traveled - (distanceBefore * 1.0) / 1000 &&
1126
- shapesGeometryAnchorPoints[i].shape_dist_traveled <=
1127
- s.shape_dist_traveled + (distanceAfter * 1.0) / 1000) {
1128
- const thisDistanceToStop = Math.abs(shapesGeometryAnchorPoints[i].shape_dist_traveled - s.shape_dist_traveled);
1129
- if (r.distanceToStop > thisDistanceToStop) {
1130
- r.distanceToStop = thisDistanceToStop;
1131
- r.this_stop_sequence = s.stop_sequence;
1132
- r.at_stop = true;
1133
- }
1134
- }
1135
- return r;
1136
- }, { at_stop: false, stop_sequence: null, distanceToStop: Infinity, this_stop_sequence: null });
1137
- let lastStopSequence;
1138
- let nextStopSequence;
1139
- // SET THIS STOP
1140
- if (stopNearby.at_stop) {
1141
- lastStopSequence = Math.min(stopNearby.this_stop_sequence, stopSequenceMaximum);
1142
- nextStopSequence = Math.min(stopNearby.this_stop_sequence + 1, stopSequenceMaximum);
1143
- }
1144
- else {
1145
- const lastNextSequences = stopTimes.stop_times.reduce((r, s, j) => {
1146
- if (j < stopTimes.stop_times.length - 1) {
1147
- if (shapesGeometryAnchorPoints[i].shape_dist_traveled >= s.shape_dist_traveled &&
1148
- shapesGeometryAnchorPoints[i].shape_dist_traveled <=
1149
- stopTimes.stop_times[j + 1].shape_dist_traveled) {
1150
- r.last_stop_sequence = j + 1;
1151
- r.next_stop_sequence = j + 1 + 1;
1152
- }
1153
- }
1154
- return r;
1155
- }, { last_stop_sequence: 1, next_stop_sequence: 2 });
1156
- lastStopSequence = lastNextSequences.last_stop_sequence;
1157
- nextStopSequence = lastNextSequences.next_stop_sequence;
1158
- }
1193
+ const distanceBefore = 0.1;
1194
+ const distanceAfter = 0.05;
1195
+ for (let i = 0; i < shapesGeometryAnchorPoints.length; i++) {
1159
1196
  let bearing = 0;
1160
1197
  // add bearing from shape computed for previous shapePoint
1161
1198
  if (i > 0) {
@@ -1173,13 +1210,37 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
1173
1210
  bearing = shapePoints[i - 1].bearing;
1174
1211
  }
1175
1212
  }
1213
+ // init values
1214
+ let thisStopSequence = null;
1215
+ let lastStopSequence = shapesGeometryAnchorPoints[i].last_stop_sequence;
1216
+ let nextStopSequence = shapesGeometryAnchorPoints[i].next_stop_sequence;
1217
+ // these are origin stop_times shape point fron shapes table, clearly at stops points
1218
+ if (shapesGeometryAnchorPoints[i].this_stop_sequence) {
1219
+ thisStopSequence = shapesGeometryAnchorPoints[i].this_stop_sequence;
1220
+ }
1221
+ // if is shape anchor point in close distance AFTER LAST stop, then set it also AT STOP
1222
+ else if (i > 0 &&
1223
+ shapesGeometryAnchorPoints[i].shape_dist_traveled -
1224
+ stopTimes.stop_times[shapesGeometryAnchorPoints[i].last_stop_sequence - 1].shape_dist_traveled <=
1225
+ distanceAfter) {
1226
+ thisStopSequence = shapesGeometryAnchorPoints[i].last_stop_sequence;
1227
+ }
1228
+ // if is shape anchor point in close distance BEFORE NEXT stop, then set it also AT STOP
1229
+ else if (i < shapesGeometryAnchorPoints.length - 1 &&
1230
+ stopTimes.stop_times[shapesGeometryAnchorPoints[i].next_stop_sequence - 1].shape_dist_traveled -
1231
+ shapesGeometryAnchorPoints[i].shape_dist_traveled <=
1232
+ distanceBefore) {
1233
+ thisStopSequence = shapesGeometryAnchorPoints[i].next_stop_sequence;
1234
+ lastStopSequence = shapesGeometryAnchorPoints[i].next_stop_sequence;
1235
+ nextStopSequence = Math.min(shapesGeometryAnchorPoints[i].next_stop_sequence + 1, stopTimes.stop_times.length);
1236
+ }
1176
1237
  const distanceFromLastStop = Math.round((shapesGeometryAnchorPoints[i].shape_dist_traveled -
1177
1238
  stopTimes.stop_times[lastStopSequence - 1].shape_dist_traveled) *
1178
1239
  1000) / 1000;
1179
1240
  // if is trip at stop than set departure by this stop
1180
1241
  // between stops do linear interpolation from departure from last stop and arrival of next stop
1181
- const timeScheduledSeconds = stopNearby.at_stop
1182
- ? stopTimes.stop_times[lastStopSequence - 1].departure_time_seconds
1242
+ const timeScheduledSeconds = thisStopSequence !== null
1243
+ ? stopTimes.stop_times[lastStopSequence - 1].arrival_time_seconds
1183
1244
  : stopTimes.stop_times[lastStopSequence - 1].departure_time_seconds +
1184
1245
  Math.round(((stopTimes.stop_times[nextStopSequence - 1].arrival_time_seconds -
1185
1246
  stopTimes.stop_times[lastStopSequence - 1].departure_time_seconds) *
@@ -1188,29 +1249,22 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
1188
1249
  stopTimes.stop_times[lastStopSequence - 1].shape_dist_traveled));
1189
1250
  shapePoints.push({
1190
1251
  index: i,
1191
- at_stop: stopNearby.at_stop,
1252
+ at_stop: thisStopSequence !== null,
1192
1253
  bearing,
1193
1254
  coordinates: shapesGeometryAnchorPoints[i].coordinates,
1194
1255
  distance_from_last_stop: distanceFromLastStop,
1195
1256
  last_stop_sequence: lastStopSequence,
1196
1257
  next_stop_sequence: nextStopSequence,
1197
- shape_dist_traveled: shapesGeometryAnchorPoints[i].shape_dist_traveled,
1198
- this_stop_sequence: stopNearby.at_stop ? stopNearby.this_stop_sequence : null,
1258
+ shape_dist_traveled: Math.round(shapesGeometryAnchorPoints[i].shape_dist_traveled * 100000) / 100000,
1259
+ this_stop_sequence: thisStopSequence,
1199
1260
  time_scheduled_seconds: timeScheduledSeconds,
1200
1261
  });
1201
- // next step
1202
- shapesAnchorPointsIterator(i + 1, cb);
1262
+ }
1263
+ return {
1264
+ trip_id: tripId,
1265
+ stop_times: stopTimes.stop_times,
1266
+ shapes_anchor_points: shapePoints,
1203
1267
  };
1204
- return new Promise((resolve, reject) => {
1205
- shapesAnchorPointsIterator(0, () => {
1206
- // FINAL OBJECT
1207
- resolve({
1208
- trip_id: tripId,
1209
- stop_times: stopTimes.stop_times,
1210
- shapes_anchor_points: shapePoints,
1211
- });
1212
- });
1213
- });
1214
1268
  }
1215
1269
  catch (err) {
1216
1270
  helpers_1.log.error(err);
@@ -1222,75 +1276,156 @@ class VehiclePositionsWorker extends workers_1.BaseWorker {
1222
1276
  *
1223
1277
  * @param {string} shapeId - GTFS shape id
1224
1278
  * @param {ITransformedTripShape[]} shapes - GTFS shape points
1279
+ * @param {IStopTime[]} stops
1225
1280
  * @returns {IShapeGeometryAnchorPoint[]} - Result array of geometry points along trip track shape
1226
1281
  */
1227
- this.getShapesGeometryAnchorPoints = (shapeId, shapes) => __awaiter(this, void 0, void 0, function* () {
1228
- var _m;
1229
- const shapesAnchorPoints = [];
1282
+ this.getShapesGeometryAnchorPoints = (shapeId, shapes, stops) => {
1230
1283
  try {
1231
- // CREATE turf LineString OBJECT FOR GIVEN COORDINATES
1232
- const line = turf.lineString(shapes.map((p) => {
1233
- return [p.shape_pt_lon, p.shape_pt_lat];
1234
- }));
1235
- // DEFAULT step BETWEEN TWO POINTS ON PATH [km] - SHORT DISTANCE IMPACTS PROCESS DURATION
1236
- const step = config_1.config.vehiclePositions.stepBetweenPoints;
1237
- const distance = ruler.lineDistance((_m = line.geometry) === null || _m === void 0 ? void 0 : _m.coordinates);
1238
- // TODO this should a class method
1239
- const lineIterator = (i, cb) => {
1240
- var _a;
1241
- // end of iteration
1242
- if (i >= distance) {
1243
- return cb();
1284
+ let segmentShapes = [];
1285
+ // Splits shapes array to multiple arrays "delimited" by stops
1286
+ for (let st = 0; st < stops.length; st++) {
1287
+ let currentSegment = [];
1288
+ for (let sh = 0; sh < shapes.length; sh++) {
1289
+ let shapePoint = Object.assign(Object.assign({}, shapes[sh]), { shape_at_stop: false });
1290
+ if (stops[st].shape_dist_traveled === shapes[sh].shape_dist_traveled) {
1291
+ shapePoint.shape_at_stop = true;
1292
+ }
1293
+ if (st < stops.length - 1 &&
1294
+ shapePoint.shape_dist_traveled >= stops[st].shape_dist_traveled &&
1295
+ shapePoint.shape_dist_traveled <= stops[st + 1].shape_dist_traveled) {
1296
+ currentSegment.push(shapePoint);
1297
+ }
1244
1298
  }
1245
- // ADD ALL POINTS TO shapes_anchor_points
1246
- shapesAnchorPoints.push({
1247
- // RETURNS coordinates ALONG path WITH GIVER i (DISTANCE) IN 5 DECIMAL PLACES
1248
- coordinates: ruler.along((_a = line.geometry) === null || _a === void 0 ? void 0 : _a.coordinates, i).map((c) => {
1249
- return Math.round(c * 100000) / 100000;
1250
- }),
1251
- // shape_dist_traveled IS IMPORTANT FOR DECIDING LAST STOP
1252
- shape_dist_traveled: Math.round(i * 1000) / 1000, // 1 METER PRECISSION
1253
- });
1254
- // next step
1255
- lineIterator(i + step, cb);
1256
- };
1257
- return new Promise((resolve, reject) => {
1258
- lineIterator(0, () => {
1259
- resolve(shapesAnchorPoints);
1260
- });
1299
+ if (currentSegment.length) {
1300
+ segmentShapes.push(currentSegment);
1301
+ }
1302
+ }
1303
+ let segmentArr = [];
1304
+ // concat arrays of stop divided segments with distributed shape points
1305
+ for (const segment of segmentShapes) {
1306
+ segmentArr = [...segmentArr, ...this.divideSegmentLine(segment)];
1307
+ }
1308
+ // Make initial shapesGeometryAnchorPoints
1309
+ let stopCounter = 0;
1310
+ const resultArr = segmentArr.map((segment, index) => {
1311
+ let this_stop_sequence;
1312
+ let last_stop_sequence;
1313
+ let next_stop_sequence;
1314
+ if (segment.shape_at_stop) {
1315
+ stopCounter++;
1316
+ this_stop_sequence = stopCounter;
1317
+ }
1318
+ else {
1319
+ this_stop_sequence = null;
1320
+ }
1321
+ last_stop_sequence = stopCounter;
1322
+ next_stop_sequence = stopCounter + 1;
1323
+ return {
1324
+ index,
1325
+ coordinates: [
1326
+ Math.round(segment.shape_pt_lon * 100000) / 100000,
1327
+ Math.round(segment.shape_pt_lat * 100000) / 100000,
1328
+ ],
1329
+ last_stop_sequence,
1330
+ next_stop_sequence,
1331
+ shape_dist_traveled: Math.round(segment.shape_dist_traveled * 1000) / 1000,
1332
+ this_stop_sequence,
1333
+ };
1334
+ });
1335
+ stopCounter++;
1336
+ // and lastly shape point for last stop as there is no segment for last stop
1337
+ resultArr.push({
1338
+ index: resultArr.length,
1339
+ coordinates: [
1340
+ Math.round(shapes[shapes.length - 1].shape_pt_lon * 100000) / 100000,
1341
+ Math.round(shapes[shapes.length - 1].shape_pt_lat * 100000) / 100000,
1342
+ ],
1343
+ last_stop_sequence: stopCounter,
1344
+ next_stop_sequence: stopCounter,
1345
+ shape_dist_traveled: Math.round(shapes[shapes.length - 1].shape_dist_traveled * 1000) / 1000,
1346
+ this_stop_sequence: stopCounter,
1261
1347
  });
1348
+ return resultArr;
1262
1349
  }
1263
1350
  catch (err) {
1264
1351
  helpers_1.log.error(err);
1265
1352
  helpers_1.log.error(shapeId);
1266
- return shapesAnchorPoints;
1353
+ return [];
1267
1354
  }
1268
- });
1269
- this.modelPositions = new _1.VehiclePositionsPositionsModel();
1270
- this.modelStops = new _1.VehiclePositionsStopsModel();
1271
- this.modelRuns = new _1.VehiclePositionsRunsModel();
1272
- this.transformationRuns = new VehiclePositionsRunsTransformation_1.VehiclePositionsRunsTransformation();
1273
- this.modelTrips = new _1.VehiclePositionsTripsModel();
1274
- this.transformation = new _1.VehiclePositionsTransformation();
1355
+ };
1356
+ /**
1357
+ * Generates new shape points from segment of trip shape along original one
1358
+ * - preserving start and end shape points belonging to stops
1359
+ * - creates new set of shape points between stops to get same point distance along original shape
1360
+ *
1361
+ * @param {ISegmentShape[]} segmentsArr - Original shape points segment between two stops
1362
+ * @param {number} distance - Minimal distance for two consequent shape points
1363
+ * @returns {ISegmentShape[]} - Result array of new shape points
1364
+ */
1365
+ this.divideSegmentLine = (segmentArr, distance = config_1.config.vehiclePositions.stepBetweenPoints) => {
1366
+ const shapeId = segmentArr[0].shape_id;
1367
+ const shapeCount = segmentArr.length;
1368
+ const startShapeDistTraveled = segmentArr[0].shape_dist_traveled;
1369
+ const endShapeDistTraveled = segmentArr[shapeCount - 1].shape_dist_traveled;
1370
+ const shapeDistLength = endShapeDistTraveled - startShapeDistTraveled;
1371
+ const segmentLine = turf.lineString(segmentArr.map((s) => {
1372
+ return [s.shape_pt_lon, s.shape_pt_lat];
1373
+ }));
1374
+ // because we dont believe source shape distances, rather compute length with turf.js
1375
+ const segmentLength = turf.length(segmentLine);
1376
+ const middlePointsCount = Math.floor(segmentLength / distance);
1377
+ const distanceOffset = (segmentLength - distance * middlePointsCount) / 2;
1378
+ // put first shape point
1379
+ const resultSegmentPoints = [
1380
+ Object.assign(Object.assign({}, segmentArr[0]), { shape_pt_sequence: 1 }),
1381
+ ];
1382
+ // for each middle point create acording shape point with interpolated origin shape dist traveled
1383
+ for (let i = 0; i < middlePointsCount; i++) {
1384
+ const currentDistance = i * distance + distanceOffset;
1385
+ const currentDistDistance = (shapeDistLength / segmentLength) * currentDistance;
1386
+ const newPoint = turf.along(segmentLine, i * distance + distanceOffset);
1387
+ resultSegmentPoints.push({
1388
+ shape_id: shapeId,
1389
+ shape_at_stop: false,
1390
+ shape_pt_sequence: resultSegmentPoints.length + 1,
1391
+ shape_pt_lat: newPoint.geometry.coordinates[1],
1392
+ shape_pt_lon: newPoint.geometry.coordinates[0],
1393
+ shape_dist_traveled: startShapeDistTraveled + currentDistDistance,
1394
+ });
1395
+ }
1396
+ return resultSegmentPoints;
1397
+ };
1398
+ this.modelPositions = new models_2.VehiclePositionsPositionsModel();
1399
+ this.modelStops = new models_2.VehiclePositionsStopsModel();
1400
+ this.modelRuns = new models_2.VehiclePositionsRunsModel();
1401
+ this.transformationRuns = new transformations_1.VehiclePositionsRunsTransformation();
1402
+ this.modelTrips = new models_2.VehiclePositionsTripsModel();
1403
+ this.transformation = new transformations_1.VehiclePositionsTransformation();
1275
1404
  this.delayComputationTripsModel = new models_1.RedisModel(ropid_gtfs_1.RopidGTFS.delayComputationTrips.name + "Model", {
1276
1405
  decodeDataAfterGet: JSON.parse,
1277
1406
  encodeDataBeforeSave: JSON.stringify,
1278
1407
  isKeyConstructedFromData: true,
1279
1408
  prefix: ropid_gtfs_1.RopidGTFS.delayComputationTrips.mongoCollectionName,
1280
1409
  }, new golemio_validator_1.Validator(ropid_gtfs_1.RopidGTFS.delayComputationTrips.name + "ModelValidator", ropid_gtfs_1.RopidGTFS.delayComputationTrips.outputMongooseSchemaObject));
1281
- this.runTripsModel = new models_1.RedisModel(vehicle_positions_1.VehiclePositions.runs_trips.name + "Model", {
1410
+ this.runTripsModel = new models_1.RedisModel(vehicle_positions_1.VehiclePositions.runsTrips.name + "Model", {
1282
1411
  decodeDataAfterGet: JSON.parse,
1283
1412
  encodeDataBeforeSave: JSON.stringify,
1284
1413
  isKeyConstructedFromData: false,
1285
- prefix: vehicle_positions_1.VehiclePositions.runs_trips.namespacePrefix,
1286
- }, new golemio_validator_1.Validator(vehicle_positions_1.VehiclePositions.runs_trips.name + "ModelValidator", vehicle_positions_1.VehiclePositions.runs_trips.outputMongooseSchemaObject));
1414
+ prefix: vehicle_positions_1.VehiclePositions.runsTrips.namespacePrefix,
1415
+ }, new golemio_validator_1.Validator(vehicle_positions_1.VehiclePositions.runsTrips.name + "ModelValidator", vehicle_positions_1.VehiclePositions.runsTrips.outputMongooseSchemaObject));
1287
1416
  this.queuePrefix = config_1.config.RABBIT_EXCHANGE_NAME + "." + vehicle_positions_1.VehiclePositions.name.toLowerCase();
1288
1417
  this.gtfsRtModel = new models_1.RedisModel("GTFSRealTimeModel", {
1289
1418
  isKeyConstructedFromData: false,
1290
1419
  prefix: "files:gtfsRt",
1291
1420
  });
1292
- this.modelGTFSStopTimes = new RopidGTFSStopTimesModel_1.RopidGTFSStopTimesModel();
1421
+ this.tripStopsModel = new models_1.RedisModel(vehicle_positions_1.VehiclePositions.tripStops.name + "Model", {
1422
+ decodeDataAfterGet: JSON.parse,
1423
+ encodeDataBeforeSave: JSON.stringify,
1424
+ isKeyConstructedFromData: false,
1425
+ prefix: vehicle_positions_1.VehiclePositions.tripStops.namespacePrefix,
1426
+ }, new golemio_validator_1.Validator(vehicle_positions_1.VehiclePositions.tripStops.name + "ModelValidator", vehicle_positions_1.VehiclePositions.tripStops.outputMongooseSchemaObject));
1293
1427
  this.modelGTFSTrips = new RopidGTFSTripsModel_1.RopidGTFSTripsModel();
1428
+ this.modelGTFSStopTimes = new RopidGTFSStopTimesModel_1.RopidGTFSStopTimesModel();
1294
1429
  // Create trips upsert queue for preventing transaction deadlocks
1295
1430
  this.upsertTripsFQ = fastq_1.default.promise(this.modelTrips.bulkUpsert, 1);
1296
1431
  }