gtfs 4.15.4 → 4.15.6

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/dist/index.js CHANGED
@@ -3175,11 +3175,20 @@ async function prepDirectory(exportPath) {
3175
3175
  await mkdir(exportPath, { recursive: true });
3176
3176
  }
3177
3177
  async function unzip(zipfilePath, exportPath) {
3178
- const zip = new StreamZip.async({ file: zipfilePath });
3179
- await zip.extract(null, exportPath);
3180
- await zip.close();
3178
+ try {
3179
+ const zip = new StreamZip.async({ file: zipfilePath });
3180
+ await zip.extract(null, exportPath);
3181
+ await zip.close();
3182
+ } catch (error) {
3183
+ throw new Error(
3184
+ `Failed to extract zip file: ${error instanceof Error ? error.message : "Unknown error"}`
3185
+ );
3186
+ }
3181
3187
  }
3182
3188
  function generateFolderName(folderName) {
3189
+ if (!folderName || typeof folderName !== "string") {
3190
+ throw new Error("Folder name must be a non-empty string");
3191
+ }
3183
3192
  return snakeCase(sanitize(folderName));
3184
3193
  }
3185
3194
 
@@ -3198,69 +3207,63 @@ import { feature, featureCollection } from "@turf/helpers";
3198
3207
  function isValidJSON(string) {
3199
3208
  try {
3200
3209
  JSON.parse(string);
3210
+ return true;
3201
3211
  } catch (error) {
3202
3212
  return false;
3203
3213
  }
3204
- return true;
3205
3214
  }
3206
3215
  function isValidLineString(lineString) {
3207
- if (!lineString) {
3216
+ if (!lineString || lineString.length <= 1) {
3208
3217
  return false;
3209
3218
  }
3210
- if (lineString.length <= 1) {
3211
- return false;
3212
- }
3213
- if (lineString.length === 2 && lineString[0][0] === lineString[1][0] && lineString[0][1] === lineString[1][1]) {
3214
- return false;
3219
+ if (lineString.length === 2) {
3220
+ const [[x1, y1], [x2, y2]] = lineString;
3221
+ return !(x1 === x2 && y1 === y2);
3215
3222
  }
3216
3223
  return true;
3217
3224
  }
3218
3225
  function consolidateShapes(shapeGroups) {
3219
3226
  const keys = /* @__PURE__ */ new Set();
3220
3227
  const segmentsArray = shapeGroups.map(
3221
- (shapes2) => shapes2.reduce(
3222
- (memo, point, idx) => {
3223
- if (idx > 0) {
3224
- memo.push([
3225
- [shapes2[idx - 1].shape_pt_lon, shapes2[idx - 1].shape_pt_lat],
3226
- [point.shape_pt_lon, point.shape_pt_lat]
3227
- ]);
3228
- }
3229
- return memo;
3230
- },
3231
- []
3232
- )
3228
+ (shapes2) => shapes2.reduce((memo, point, idx) => {
3229
+ if (idx > 0) {
3230
+ const prevPoint = shapes2[idx - 1];
3231
+ memo.push([
3232
+ [prevPoint.shape_pt_lon, prevPoint.shape_pt_lat],
3233
+ [point.shape_pt_lon, point.shape_pt_lat]
3234
+ ]);
3235
+ }
3236
+ return memo;
3237
+ }, [])
3233
3238
  );
3234
3239
  const consolidatedLineStrings = [];
3235
3240
  for (const segments of segmentsArray) {
3236
3241
  consolidatedLineStrings.push([]);
3237
3242
  for (const segment of segments) {
3238
- const key1 = `${segment[0][0]},${segment[0][1]},${segment[1][0]},${segment[1][1]}`;
3239
- const key2 = `${segment[1][0]},${segment[1][1]},${segment[0][0]},${segment[0][1]}`;
3243
+ const key1 = segment.flat().join(",");
3244
+ const key2 = segment.reverse().flat().join(",");
3240
3245
  const currentLine = last(consolidatedLineStrings);
3241
- if (keys.has(key1) || keys.has(key2)) {
3246
+ if (!currentLine || keys.has(key1) || keys.has(key2)) {
3242
3247
  consolidatedLineStrings.push([]);
3243
- } else {
3244
- if (currentLine?.length === 0) {
3245
- currentLine.push(segment[0]);
3246
- }
3247
- currentLine?.push(segment[1]);
3248
- keys.add(key1);
3249
- keys.add(key2);
3248
+ continue;
3249
+ }
3250
+ if (currentLine.length === 0) {
3251
+ currentLine.push(segment[0]);
3250
3252
  }
3253
+ currentLine.push(segment[1]);
3254
+ keys.add(key1);
3255
+ keys.add(key2);
3251
3256
  }
3252
3257
  }
3253
3258
  return filter(consolidatedLineStrings, isValidLineString);
3254
3259
  }
3255
3260
  function formatHexColor(color) {
3256
- if (color === null || color === void 0) {
3257
- return;
3258
- }
3261
+ if (!color) return void 0;
3259
3262
  return `#${color}`;
3260
3263
  }
3261
3264
  function formatProperties(properties) {
3262
3265
  const formattedProperties = cloneDeep(
3263
- omitBy(properties, (value) => value === null || value === void 0)
3266
+ omitBy(properties, (value) => value == null)
3264
3267
  );
3265
3268
  const formattedRouteColor = formatHexColor(properties.route_color);
3266
3269
  const formattedRouteTextColor = formatHexColor(properties.route_text_color);
@@ -3294,7 +3297,7 @@ function stopsToGeoJSONFeatureCollection(stops2) {
3294
3297
  const features = compact(
3295
3298
  stops2.map((stop) => {
3296
3299
  if (!stop.stop_lon || !stop.stop_lat) {
3297
- return;
3300
+ return void 0;
3298
3301
  }
3299
3302
  return feature(
3300
3303
  {
@@ -3319,14 +3322,14 @@ import { clearLine, cursorTo } from "node:readline";
3319
3322
  import { noop } from "lodash-es";
3320
3323
  import * as colors from "yoctocolors";
3321
3324
  function log(config) {
3322
- if (config.verbose === false) {
3325
+ if (!config.verbose) {
3323
3326
  return noop;
3324
3327
  }
3325
3328
  if (config.logFunction) {
3326
3329
  return config.logFunction;
3327
3330
  }
3328
- return (text, overwrite) => {
3329
- if (overwrite === true && process.stdout.isTTY) {
3331
+ return (text, overwrite = false) => {
3332
+ if (overwrite && process.stdout.isTTY) {
3330
3333
  clearLine(process.stdout, 0);
3331
3334
  cursorTo(process.stdout, 0);
3332
3335
  } else {
@@ -3356,16 +3359,12 @@ ${formatError(text)}
3356
3359
  };
3357
3360
  }
3358
3361
  function formatWarning(text) {
3359
- const warningMessage = `${colors.underline("Warning")}: ${text}`;
3360
- return colors.yellow(warningMessage);
3362
+ return colors.yellow(`${colors.underline("Warning")}: ${text}`);
3361
3363
  }
3362
3364
  function formatError(error) {
3363
3365
  const messageText = error instanceof Error ? error.message : error;
3364
- const errorMessage = `${colors.underline("Error")}: ${messageText.replace(
3365
- "Error: ",
3366
- ""
3367
- )}`;
3368
- return colors.red(errorMessage);
3366
+ const cleanMessage = messageText.replace(/^Error:\s*/i, "");
3367
+ return colors.red(`${colors.underline("Error")}: ${cleanMessage}`);
3369
3368
  }
3370
3369
 
3371
3370
  // src/lib/utils.ts
@@ -3398,14 +3397,17 @@ function setDefaultConfig(initialConfig) {
3398
3397
  }
3399
3398
  function convertLongTimeToDate(longDate) {
3400
3399
  const { high, low, unsigned } = longDate;
3401
- return new Date(new Long(low, high, unsigned).toInt() * 1e3).toISOString();
3400
+ return new Date(
3401
+ Long.fromBits(low, high, unsigned).toNumber() * 1e3
3402
+ ).toISOString();
3402
3403
  }
3403
3404
  function calculateSecondsFromMidnight(time) {
3404
- const split = time.split(":").map((d) => Number.parseInt(d, 10));
3405
- if (split.length !== 3) {
3405
+ if (!time || typeof time !== "string") return null;
3406
+ const [hours, minutes, seconds] = time.split(":").map(Number);
3407
+ if ([hours, minutes, seconds].some(isNaN) || hours >= 24 || minutes >= 60 || seconds >= 60) {
3406
3408
  return null;
3407
3409
  }
3408
- return split[0] * 3600 + split[1] * 60 + split[2];
3410
+ return hours * 3600 + minutes * 60 + seconds;
3409
3411
  }
3410
3412
  function padLeadingZeros(time) {
3411
3413
  const split = time.split(":").map((d) => String(Number(d)).padStart(2, "0"));
@@ -3437,19 +3439,22 @@ function degree2radian(angle) {
3437
3439
  function radian2degree(angle) {
3438
3440
  return angle / Math.PI * 180;
3439
3441
  }
3442
+ var EARTH_RADIUS_METERS = 6371e3;
3440
3443
  function formatWhereClauseBoundingBox(latitudeDegree, longitudeDegree, boundingBoxSideMeters) {
3441
- const earthRadius = 6371e3;
3442
- latitudeDegree = typeof latitudeDegree === "string" ? parseFloat(latitudeDegree) : latitudeDegree;
3443
- longitudeDegree = typeof longitudeDegree === "string" ? parseFloat(longitudeDegree) : longitudeDegree;
3444
- const latitudeRadian = degree2radian(latitudeDegree);
3445
- const radiusFromLatitude = Math.cos(latitudeRadian) * earthRadius;
3446
- const deltaLatitude = radian2degree(boundingBoxSideMeters / 2 / earthRadius);
3447
- const deltaLongitude = radian2degree(
3448
- boundingBoxSideMeters / 2 / radiusFromLatitude
3449
- );
3450
- let query = `stop_lat BETWEEN ${latitudeDegree - deltaLatitude} AND ${latitudeDegree + deltaLatitude}`;
3451
- query += ` AND stop_lon BETWEEN ${longitudeDegree - deltaLongitude} AND ${longitudeDegree + deltaLongitude}`;
3452
- return query;
3444
+ const lat = Number(latitudeDegree);
3445
+ const lon = Number(longitudeDegree);
3446
+ if (isNaN(lat) || isNaN(lon) || lat < -90 || lat > 90 || lon < -180 || lon > 180) {
3447
+ throw new Error("Invalid latitude or longitude values");
3448
+ }
3449
+ const latitudeRadian = degree2radian(lat);
3450
+ const radiusFromLatitude = Math.cos(latitudeRadian) * EARTH_RADIUS_METERS;
3451
+ const halfSide = boundingBoxSideMeters / 2;
3452
+ const deltaLatitude = radian2degree(halfSide / EARTH_RADIUS_METERS);
3453
+ const deltaLongitude = radian2degree(halfSide / radiusFromLatitude);
3454
+ return [
3455
+ `stop_lat BETWEEN ${lat - deltaLatitude} AND ${lat + deltaLatitude}`,
3456
+ `stop_lon BETWEEN ${lon - deltaLongitude} AND ${lon + deltaLongitude}`
3457
+ ].join(" AND ");
3453
3458
  }
3454
3459
  function formatWhereClause(key, value) {
3455
3460
  if (Array.isArray(value)) {
@@ -3485,7 +3490,7 @@ function formatOrderByClause(orderBy2) {
3485
3490
  return orderByClause;
3486
3491
  }
3487
3492
  function getDayOfWeekFromDate(date) {
3488
- const daysOfWeek = [
3493
+ const DAYS_OF_WEEK = [
3489
3494
  "sunday",
3490
3495
  "monday",
3491
3496
  "tuesday",
@@ -3494,10 +3499,17 @@ function getDayOfWeekFromDate(date) {
3494
3499
  "friday",
3495
3500
  "saturday"
3496
3501
  ];
3497
- const dateString = date.toString().replace(/(\d{4})(\d{2})(\d{2})/, "$1-$2-$3");
3498
- const [year, month, day] = dateString.split("-").map(Number);
3499
- const dayOfWeek = new Date(year, month - 1, day).getDay();
3500
- return daysOfWeek[dayOfWeek];
3502
+ if (!Number.isInteger(date) || date.toString().length !== 8) {
3503
+ throw new Error("Date must be in YYYYMMDD format");
3504
+ }
3505
+ const year = Math.floor(date / 1e4);
3506
+ const month = Math.floor(date % 1e4 / 100);
3507
+ const day = date % 100;
3508
+ const dateObj = new Date(year, month - 1, day);
3509
+ if (dateObj.toString() === "Invalid Date") {
3510
+ throw new Error("Invalid date");
3511
+ }
3512
+ return DAYS_OF_WEEK[dateObj.getDay()];
3501
3513
  }
3502
3514
 
3503
3515
  // src/lib/import-gtfs-realtime.ts
@@ -4018,6 +4030,9 @@ var importGtfsFiles = (db, task) => mapSeries2(
4018
4030
  task.log(`Importing - ${filename}\r`);
4019
4031
  const columns = model.schema.filter((column) => column.name !== "id");
4020
4032
  const placeholder = columns.map(({ name }) => `@${name}`).join(", ");
4033
+ const prefixedColumns = new Set(
4034
+ columns.filter((col) => col.prefix).map((col) => col.name)
4035
+ );
4021
4036
  const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map((column) => column.name).join(", ")}) VALUES (${placeholder})`;
4022
4037
  const insert = db.prepare(prepareStatement);
4023
4038
  const insertLines = db.transaction((lines) => {
@@ -4031,7 +4046,7 @@ var importGtfsFiles = (db, task) => mapSeries2(
4031
4046
  line
4032
4047
  ).map(([columnName, value]) => [
4033
4048
  columnName,
4034
- columns.find((col) => col.name === columnName)?.prefix ? `${task.prefix}${value}` : value
4049
+ prefixedColumns.has(columnName) && value !== null ? `${task.prefix}${value}` : value
4035
4050
  ])
4036
4051
  );
4037
4052
  insert.run(prefixedLine);