Skip to content

Editing with CRUD

CRUD commands treat a GTFS feed like a small relational database. This guide walks through a realistic scenario: renaming a stop, fixing a mis-classified route, and retiring a line — all while keeping the feed valid at every step.

The running example uses a feed called gtfs.zip in the current directory. Swap in your own path as you follow along.

You maintain a transit feed with tens of thousands of stops. A recent audit flagged three issues to fix:

  1. Stop S01 should be renamed from “Place” to “Gare Centrale”.
  2. A bus route is mis-encoded as route_type=700 (cable tram) and needs to be 3 (bus).
  3. An old line OLD_LINE is being retired — every trip, and the rows that reference them, should be removed.

Never update what you have not verified first. The read command is read-only and takes the same --where clause as update and delete.

Terminal window
gapline read stops -f gtfs.zip --where "stop_id=S01"

Output confirms exactly one row, with the fields you expect:

stop_id | stop_name | stop_lat | stop_lon | ...
S01 | Place | 45.5017 | -73.5673 | ...

If the match set is larger than you think, tighten the filter:

Terminal window
gapline read stops -f gtfs.zip --where "stop_name LIKE Place%" | wc -l

A straight field rewrite — stop_name is not a primary key, so no cascade is needed.

Terminal window
gapline update stops -f gtfs.zip \
--where "stop_id=S01" \
--set stop_name="Gare Centrale" \
--confirm

Re-read to confirm:

Terminal window
gapline read stops -f gtfs.zip --where "stop_id=S01"

Same shape — single-field, non-PK update. The --where matches any route with the wrong type, which may be one or several.

Terminal window
gapline update routes -f gtfs.zip \
--where "route_type=700" \
--set route_type=3 \
--confirm

If the run reported “NO_CHANGES” (exit code 4), nothing matched — the audit’s fix list was already applied in a previous pass.

This is the risky step. Removing a route_id means every trip tied to it has to go, and every stop_times row that references those trips has to go. gapline computes this chain automatically.

  1. Preview the blast radius first — read-only, no surprises:

    Terminal window
    gapline read trips -f gtfs.zip --where "route_id=OLD_LINE" | wc -l
  2. Delete the trips — the cascade preview lists every dependent file that will lose rows:

    Terminal window
    gapline delete trips -f gtfs.zip --where "route_id=OLD_LINE"
    Records to delete from trips.txt:
    OLD_LINE-T01
    OLD_LINE-T02
    ... and 47 more
    Deleting would also delete:
    - 1 243 records in stop_times.txt
    - 12 records in frequencies.txt
    Proceed with cascade delete? [y/N] y
  3. Remove the route itself — it no longer has trips referencing it, so there is no cascade:

    Terminal window
    gapline delete routes -f gtfs.zip --where "route_id=OLD_LINE" --confirm

Before shipping the modified feed, run the full validation suite to confirm nothing has slipped:

Terminal window
gapline validate -f gtfs.zip --min-severity warning

If the goal is a separate output file, add --output gtfs-patched.zip to the final update/delete or pass it to a .gl script. See writing .gl scripts for a version of this whole walkthrough as a single batch file that keeps the source feed intact until the save directive succeeds.

IntentCommand
See what will be affected.gapline read <target> --where "…"
Edit a field (non-PK).gapline update <target> --where "…" --set field=value --confirm
Rename a primary key across the whole feed.gapline update <target> --where "…" --set id=new_id --cascade --confirm
Remove records and their dependents.gapline delete <target> --where "…" (preview) or … --confirm (auto-yes)
Write to a new file instead of in place.Add -o newfeed.zip to any of the above.