analytics + improve GTFS importer
This commit is contained in:
4 files changed
+67
-30
No files matched your search
@@ -21,7 +21,13 @@
|
|||||||
"workspace": {
|
"workspace": {
|
||||||
"packageJson": {
|
"packageJson": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"npm:astro@^4.15.11"
|
"npm:@astrojs/svelte@^5.7.1",
|
||||||
|
"npm:@astrojs/tailwind@^5.1.1",
|
||||||
|
"npm:astro@^4.15.11",
|
||||||
|
"npm:gsap@^3.12.5",
|
||||||
|
"npm:svelte@^4.2.19",
|
||||||
|
"npm:tailwindcss@^3.4.13",
|
||||||
|
"npm:typescript@^5.6.2"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-28
@@ -1,6 +1,7 @@
|
|||||||
import { TextWriter } from "jsr:@zip-js/zip-js";
|
import { TextWriter } from "jsr:@zip-js/zip-js";
|
||||||
import { ZipReader } from "jsr:@zip-js/zip-js";
|
import { ZipReader } from "jsr:@zip-js/zip-js";
|
||||||
import { parse } from "npm:csv-parse/sync";
|
import { parse } from "npm:csv-parse/sync";
|
||||||
|
import { parse as parseStream } from "npm:csv-parse";
|
||||||
|
|
||||||
function indexRecords(records, key) {
|
function indexRecords(records, key) {
|
||||||
const result = {};
|
const result = {};
|
||||||
@@ -12,46 +13,66 @@ function indexRecords(records, key) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function streamRecords(entry, filter) {
|
||||||
|
const records = [];
|
||||||
|
|
||||||
|
const parser = parseStream({ columns: true });
|
||||||
|
|
||||||
|
parser.on("readable", () => {
|
||||||
|
let record;
|
||||||
|
while ((record = parser.read()) !== null) {
|
||||||
|
// console.log(`got record ${record.trip_id}`);
|
||||||
|
|
||||||
|
if (filter(record)) records.push(record);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await entry.getData(
|
||||||
|
new WritableStream({
|
||||||
|
write(chunk) {
|
||||||
|
parser.write(chunk);
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
parser.end();
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
const reader = new ZipReader((await Deno.open(Deno.args[0])).readable);
|
const reader = new ZipReader((await Deno.open(Deno.args[0])).readable);
|
||||||
|
|
||||||
const files = await reader.getEntries();
|
const files = await reader.getEntries();
|
||||||
|
|
||||||
const wantedFiles = [
|
const wantedFiles = ["routes", "trips", "stops", "calendar"];
|
||||||
"routes",
|
|
||||||
"trips",
|
|
||||||
"stops",
|
|
||||||
"stop_times",
|
|
||||||
"calendar",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const entries: Partial<Record<(typeof wantedFiles)[number], any[]>> = {};
|
const entries = {};
|
||||||
|
|
||||||
console.log("reading files");
|
console.log("reading files");
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
files
|
files
|
||||||
.filter((f) => {
|
.filter((f) => {
|
||||||
//@ts-ignore
|
|
||||||
return wantedFiles.includes(f.filename.replace(".txt", ""));
|
return wantedFiles.includes(f.filename.replace(".txt", ""));
|
||||||
})
|
})
|
||||||
.map(async (f) => {
|
.map(async (f) => {
|
||||||
const writer = new TextWriter();
|
const writer = new TextWriter();
|
||||||
entries[f.filename.replace(".txt", "") as (typeof wantedFiles)[number]] =
|
entries[f.filename.replace(".txt", "")] = parse(await f.getData(writer), {
|
||||||
parse(await f.getData!(writer), { columns: true });
|
columns: true,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
reader.close();
|
|
||||||
|
|
||||||
console.log(`Indexing ${entries.stops?.length} stops...`);
|
console.log(`Indexing ${entries.stops?.length} stops...`);
|
||||||
|
|
||||||
function actualStop(stop_id: string): string {
|
function actualStop(stop_id) {
|
||||||
return indexedStops[stop_id]?.parent_station || stop_id;
|
return indexedStops[stop_id]?.parent_station || stop_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexedStops = indexRecords(entries.stops, "stop_id");
|
const indexedStops = indexRecords(entries.stops, "stop_id");
|
||||||
|
|
||||||
const metroRoutes = entries.routes!.filter(
|
const metroRoutes = entries.routes.filter(
|
||||||
(r) => r.route_type == 1 || r.route_type == 0
|
(r) => r.route_type == 1 || r.route_type == 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -59,16 +80,21 @@ const metroRouteIds = metroRoutes.map((r) => r.route_id);
|
|||||||
|
|
||||||
console.log(`Indexing trips...`);
|
console.log(`Indexing trips...`);
|
||||||
|
|
||||||
const trips = entries.trips!.filter(
|
const trips = entries.trips.filter(
|
||||||
(t) => t.service_id == "canonical" && metroRouteIds.includes(t.route_id)
|
(t) => t.service_id == "canonical" && metroRouteIds.includes(t.route_id)
|
||||||
);
|
);
|
||||||
const indexedTrips = indexRecords(trips, "trip_id");
|
const indexedTrips = indexRecords(trips, "trip_id");
|
||||||
|
|
||||||
console.log("sorting stop times");
|
console.log("fetching stop times");
|
||||||
|
|
||||||
const stop_times = entries
|
entries.stop_times = await streamRecords(
|
||||||
.stop_times!.filter((s) => s.trip_id in indexedTrips)
|
files.find((f) => f.filename == "stop_times.txt"),
|
||||||
.sort((a, b) => {
|
(s) => s.trip_id in indexedTrips
|
||||||
|
);
|
||||||
|
|
||||||
|
reader.close();
|
||||||
|
|
||||||
|
const stop_times = entries.stop_times.sort((a, b) => {
|
||||||
if (a.trip_id < b.trip_id) {
|
if (a.trip_id < b.trip_id) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (a.trip_id > b.trip_id) {
|
} else if (a.trip_id > b.trip_id) {
|
||||||
@@ -76,9 +102,9 @@ const stop_times = entries
|
|||||||
} else {
|
} else {
|
||||||
return a.stop_sequence - b.stop_sequence;
|
return a.stop_sequence - b.stop_sequence;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const edges: Record<string, any> = {};
|
const edges = {};
|
||||||
|
|
||||||
console.log("calculating edges...");
|
console.log("calculating edges...");
|
||||||
|
|
||||||
@@ -126,10 +152,4 @@ const output = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(
|
|
||||||
entries.calendar?.sort(
|
|
||||||
(a, b) => parseInt(b.end_date) - parseInt(a.end_date)
|
|
||||||
)[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
await Deno.writeTextFile("cities/boston.json", JSON.stringify(output));
|
await Deno.writeTextFile("cities/boston.json", JSON.stringify(output));
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import agency from "../../cities/boston.json"
|
import agency from "../agency"
|
||||||
|
|
||||||
export let name
|
export let name
|
||||||
export let compact = false
|
export let compact = false
|
||||||
|
|||||||
@@ -7,6 +7,17 @@ import screenshot from "../screenshot.png";
|
|||||||
<head>
|
<head>
|
||||||
<link rel="preload" as="image" href={screenshot.src} />
|
<link rel="preload" as="image" href={screenshot.src} />
|
||||||
<title>The Boston Train Quiz</title>
|
<title>The Boston Train Quiz</title>
|
||||||
|
|
||||||
|
{
|
||||||
|
import.meta.env.PROD && (
|
||||||
|
<script
|
||||||
|
is:inline
|
||||||
|
defer
|
||||||
|
src="https://cloud.umami.is/script.js"
|
||||||
|
data-website-id="3c0255b3-e006-4147-8fc3-5d87b9160e7b"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<Guesser client:load />
|
<Guesser client:load />
|
||||||
|
|||||||
Reference in new issue
Block a user