Load from gtfs atlas lookup #36
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
gtfs.zone/coloring-book#36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add two new options to the Load dropdown: From URL (paste a direct zip URL) and Search Atlas (search transitland-atlas feeds). A build-time script processes the transitland-atlas DMFR JSON files into a compact, lazily-loaded
public/atlas-feeds.json. The UI is transparent — users see exactly which URL they're loading from. The atlas modal shows all available DMFR metadata so users can make informed choices before loading.Key tradeoffs considered: client-side filtering is fine given the file is ~a few hundred KB after processing; no server needed; the script runs manually and its output is committed.
Relevant Context
Files to modify:
src/index.html(lines 163–245) — Load dropdown<ul id="load-dropdown">, add two new<li>items: "From URL" and "Search Atlas"src/modules/ui.ts—setupEventListeners()adds click handlers for the two new buttons;loadGTFSFromURL(url)already exists and is the target call for both flowssrc/modules/modal-utils.ts—showModal()supports an HTML string body +onMounthook; sufficient for "From URL"; atlas search needs a richer custom modalFiles to create:
scripts/generate-atlas-data.ts— Node/tsx script: fetch transitland-atlas feeds via GitHub API, extract GTFS static feeds, writepublic/atlas-feeds.jsonsrc/modules/atlas-search.ts—showAtlasSearchModal(): custom modal (notshowModal) with search input, lazy-loaded results, click-to-load behavior; returns the selected URL or nullpublic/atlas-feeds.json— committed generated file; lazy-fetched byatlas-search.tson first openKey patterns:
closeLoadDropdown()helper insetupEventListeners()blurs the active elementthis.loadGTFSFromURL(url)inUIControllermodal-utils.ts:document.createElement('div'), append todocument.body, remove on closenotifications.showSuccess/showError()DMFR schema (transitland-atlas): each JSON file has
feeds[]withid,spec(gtfs|gtfs-rt| ...),urls.static_current,name; andoperators[]withname,short_name,tags.country_code,tags.metro_area.Phase 1: Data pipeline — generate
atlas-feeds.jsonWrite a script that pulls the transitland-atlas feed data and emits a compact, searchable JSON file committed to the repo.
Why first: the atlas search UI depends on this file existing; locking the data format before building the UI avoids rework.
Results: 743 DMFR files processed, 3774 GTFS feeds written, 1994 non-GTFS/no-URL entries skipped, 0 errors. Output is 758KB minified. 1363 of 3774 feeds have operator_name populated; location is sparse (many operators don't set metro_area/country_code). Fetches from
raw.githubusercontent.comin batches of 10 — unauthenticated is fine at this scale, but GITHUB_TOKEN env var is supported.raw.githubusercontent.comreturns JSON directly (no Content-Type negotiation needed) unlike the API endpoint.scripts/generate-atlas-data.ts:transitland/transitland-atlasat pathfeeds/using the GitHub API (https://api.github.com/repos/transitland/transitland-atlas/git/trees/HEAD?recursive=1), then fetch each.jsonfile underfeeds/feeds[], keep only entries wherespec === 'gtfs'andurls.static_currentis non-empty{ id: string, name: string, operator_name: string, location: string, url: string }id: the feed's DMFRidfieldname:feed.name(may be empty — fall back toid)operator_name: joinoperators[].namefor operators associated with this feed (useassociated_feedsor just the firstoperators[]entry per file)location:operator.tags?.metro_areaoroperator.tags?.country_codeor empty stringurl:feed.urls.static_currentpublic/atlas-feeds.jsonpackage.json:"atlas": "tsx scripts/generate-atlas-data.ts"(run withnpm run atlas)npm run atlasand commit the generatedpublic/atlas-feeds.jsonGotchas:
GITHUB_TOKENenv var if present (Authorization: Bearer $GITHUB_TOKEN).operatorsandfeedscan have many-to-many relationships. Keep it simple: for each feed, look for the firstoperators[]entry in the same DMFR file to get the operator name and location. Don't try to resolve cross-file references.Phase 2: "From URL" dropdown item + modal
Add a simple "paste a URL" option to the Load dropdown.
Why second: self-contained, no dependencies on Phase 1, and useful standalone.
src/index.html, add a new<li>to#load-dropdownafter the Upload item:src/modules/ui.tssetupEventListeners(), add a click handler for#from-url-btn:closeLoadDropdown()this.showFromURLModal()showFromURLModal()method toUIControllerinui.ts:showModal()frommodal-utils.ts<input id="gtfs-url-input" type="url" class="input input-bordered w-full" placeholder="https://example.com/gtfs.zip" />[{ label: 'Cancel' }, { label: 'Load', className: 'btn-primary', onClick: async () => { ... } }]onClick: read(document.getElementById('gtfs-url-input') as HTMLInputElement).value, validate non-empty, callthis.loadGTFSFromURL(url), return false to closetrue(keep modal open — existing pattern from modal-utils)onMountto focus the input and wire up Enter key to trigger LoadPhase 3: "Search Atlas" dropdown item + modal
Add the full atlas search experience: lazy-load the feed list, filter by text input, display all DMFR metadata, click to load.
Why third: depends on Phase 1 (data file) and benefits from the modal pattern established in Phase 2.
src/index.html, add another<li>to#load-dropdownafter "From URL":src/modules/atlas-search.ts:interface AtlasFeed { id: string; name: string; operator_name: string; location: string; url: string }let cachedFeeds: AtlasFeed[] | null = nullasync function loadAtlasFeeds(): Promise<AtlasFeed[]>— fetches/atlas-feeds.jsononce, caches result; shows a loading state in the modal while fetchingexport async function showAtlasSearchModal(): Promise<string | null>— builds and shows a custom modal, returns the selected URL or null on cancelshowModaldoes):filterAndRender(query, feeds): filter feeds where name/operator_name/location/url contains query (case-insensitive); render up to 50 results as clickable rows showing: feed name (bold), operator name, location, URL (truncated,text-xs text-base-content/60); clicking a row resolves the modal with that URLsrc/modules/ui.tssetupEventListeners(), add click handler for#atlas-search-btn:closeLoadDropdown()const url = await showAtlasSearchModal()this.loadGTFSFromURL(url)showAtlasSearchModalinui.tsGotchas:
document.body.removeChild(modal).#atlas-resultsrather than leaving it blank./atlas-feeds.jsonworks in both dev (Vite servespublic/) and prod (dist/includespublic/contents).Original Issue
This is important for initial demo purposes. At the moment, the two feeds are great, but we should be able to load any feed from transitland atlas: https://github.com/transitland/transitland-atlas To do this, we need to have a searchable list of urls.
Notes:
UI: We use a lot of modals, so lets continue that. Load should still be a dropdown with simple options: New feed, Upload, From URL (lets add this while we're at it), and Search Atlas
From url and search atlas should open modals. From url will just be a place where you can paste the url. Search atlas will give a search box and you can select a feed. Lets give the user all the info we have from transitland, then they can click to load