|
| 1 | +import fetch from "node-fetch" |
| 2 | +import fs from "fs" |
| 3 | +import circle from "@turf/circle" |
| 4 | +import cliProgress from "cli-progress" |
| 5 | +import PQueue from "p-queue" |
| 6 | + |
| 7 | +// Queue used to rate limit our requests |
| 8 | +// At most 5 requests at the same time, with at most 5 requests per second |
| 9 | +const queue = new PQueue({ concurrency: 5, interval: 1000, intervalCap: 5 }) |
| 10 | + |
| 11 | +// create new progress bar |
| 12 | +const progressBar = new cliProgress.SingleBar({ |
| 13 | + format: "Route coordinates", |
| 14 | + barCompleteChar: "\u2588", |
| 15 | + barIncompleteChar: "\u2591", |
| 16 | + hideCursor: true, |
| 17 | +}) |
| 18 | + |
| 19 | +const promiseStops = fetch( |
| 20 | + "http://www.poatransporte.com.br/php/facades/process.php?a=tp&p=" |
| 21 | +).then((r) => r.json()) |
| 22 | +const promiseRoutes = fetch( |
| 23 | + "http://www.poatransporte.com.br/php/facades/process.php?a=nc&p=%&t=o" |
| 24 | +).then((r) => r.json()) |
| 25 | +// If we want routes for lotations, we can use http://www.poatransporte.com.br/php/facades/process.php?a=nc&p=%&t=l |
| 26 | + |
| 27 | +const [stops, routes] = await Promise.all([promiseStops, promiseRoutes]) |
| 28 | +console.log("Fetched stops and routes") |
| 29 | + |
| 30 | +// Create an O(1) access to stops using their code |
| 31 | +const stopsDict = {} |
| 32 | +for (const stop of stops) { |
| 33 | + const contour = circle( |
| 34 | + { |
| 35 | + type: "Feature", |
| 36 | + geometry: { |
| 37 | + type: "Point", |
| 38 | + coordinates: [parseFloat(stop.latitude), parseFloat(stop.longitude)], |
| 39 | + }, |
| 40 | + }, |
| 41 | + 0.015, |
| 42 | + { steps: 3 } |
| 43 | + ) |
| 44 | + |
| 45 | + stop.contour = contour.geometry.coordinates |
| 46 | + stop.usedLevels = new Set() |
| 47 | + stopsDict[stop.codigo] = stop |
| 48 | +} |
| 49 | +console.log("Computed stops data") |
| 50 | + |
| 51 | +// Create an O(1) access to routes using their id, and also fetch their value |
| 52 | +const routesDict = {} |
| 53 | +progressBar.start(routes.length, 0) |
| 54 | +for (const route of routes) { |
| 55 | + new Promise(async (resolve, reject) => { |
| 56 | + const routeInfo = await queue.add(() => |
| 57 | + fetch( |
| 58 | + `http://www.poatransporte.com.br/php/facades/process.php?a=il&p=${route.id}` |
| 59 | + ).then((r) => r.json()) |
| 60 | + ) |
| 61 | + |
| 62 | + const path = Object.entries(routeInfo) |
| 63 | + .filter((obj) => !isNaN(parseInt(obj[0]))) |
| 64 | + .map((obj) => obj[1]) |
| 65 | + .map(({ lat, lng }) => [lat, lng]) |
| 66 | + |
| 67 | + route.path = path |
| 68 | + route.stops = new Set() |
| 69 | + routesDict[route.id] = route |
| 70 | + |
| 71 | + // Make sure to increment the progress bar |
| 72 | + progressBar.increment() |
| 73 | + resolve(true) |
| 74 | + }) |
| 75 | +} |
| 76 | +console.log("Scheduled routes data") |
| 77 | + |
| 78 | +// Just make sure every request was fulfilled |
| 79 | +await queue.onIdle() |
| 80 | +progressBar.stop() |
| 81 | +console.log("Computed routes data") |
| 82 | + |
| 83 | +// Fill the table of stops for every route |
| 84 | +for (const stop of stops) { |
| 85 | + for (const line of stop.linhas) { |
| 86 | + const route = routesDict[line.idLinha] |
| 87 | + if (route) route.stops.add(stop.codigo) |
| 88 | + } |
| 89 | +} |
| 90 | +console.log("Filled stops table for every route") |
| 91 | + |
| 92 | +// Fill the table of levels for every route |
| 93 | +for (const route of routes) { |
| 94 | + const routeStops = Array.from(route.stops, (stopId) => stopsDict[stopId]) |
| 95 | + for (let level = 0; !route.level; level++) { |
| 96 | + const hasUsedLevel = routeStops.some((stop) => stop.usedLevels.has(level)) |
| 97 | + if (!hasUsedLevel) route.level = level |
| 98 | + } |
| 99 | + |
| 100 | + // Fill for every stop, that we used this level |
| 101 | + for (const stop of routeStops) { |
| 102 | + stop.usedLevels.add(route.level) |
| 103 | + } |
| 104 | +} |
| 105 | +console.log("Filled table of levels for every route") |
| 106 | + |
| 107 | +// Compute every missing information to save on the file |
| 108 | +const saveableStops = stops.map((stop) => ({ |
| 109 | + ...stop, |
| 110 | + level: Math.max(...stop.usedLevels), |
| 111 | + usedLevels: undefined, |
| 112 | +})) |
| 113 | +const saveableRoutes = routes.map((route) => ({ |
| 114 | + ...route, |
| 115 | + stops: undefined, |
| 116 | + path: route.path.map((coordinate) => [ |
| 117 | + ...coordinate, |
| 118 | + (route.level - 1) * 100, |
| 119 | + ]), |
| 120 | +})) |
| 121 | +const levels = Object.fromEntries( |
| 122 | + saveableStops.map((stop) => [stop.codigo, stop.level]) |
| 123 | +) |
| 124 | + |
| 125 | +fs.writeFileSync("data/routes.min.json", JSON.stringify(saveableRoutes)) |
| 126 | +fs.writeFileSync("data/routes.json", JSON.stringify(saveableRoutes, null, 2)) |
| 127 | +fs.writeFileSync("data/stops.min.json", JSON.stringify(saveableStops)) |
| 128 | +fs.writeFileSync("data/stops.json", JSON.stringify(saveableStops, null, 2)) |
| 129 | +fs.writeFileSync("data/levels.min.json", JSON.stringify(levels)) |
| 130 | +fs.writeFileSync("data/levels.json", JSON.stringify(levels, null, 2)) |
| 131 | +console.log("Every file was written!") |
| 132 | + |
| 133 | +// Extra information about the files |
| 134 | +const maxLevel = Math.max(...Object.values(levels)) |
| 135 | +console.log(`MAX LEVEL: ${maxLevel}`) |
0 commit comments