-
Notifications
You must be signed in to change notification settings - Fork 111
Docs and CLI Improvements #471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,111 +2,129 @@ | |
|
|
||
| ## Introduction | ||
|
|
||
| The `onebusaway-gtfs-merge-cli` command-line application is a simple command-line tool for merging | ||
| [GTFS](https://developers.google.com/transit/gtfs) feeds. | ||
| The `onebusaway-gtfs-merge-cli` command-line application is a tool for merging two or more | ||
| [GTFS](https://developers.google.com/transit/gtfs) feeds into a single feed. | ||
|
|
||
| ## Getting the Application | ||
|
|
||
| You can download the application from Maven Central. | ||
|
|
||
| Go to https://repo1.maven.org/maven2/org/onebusaway/onebusaway-gtfs-merge-cli/, select the version | ||
| you want and get the URL for the largest jar file. An example would be | ||
| https://repo1.maven.org/maven2/org/onebusaway/onebusaway-gtfs-merge-cli/3.2.2/onebusaway-gtfs-merge-cli-3.2.2.jar | ||
| https://repo1.maven.org/maven2/org/onebusaway/onebusaway-gtfs-merge-cli/14.0.0/onebusaway-gtfs-merge-cli-14.0.0.jar | ||
|
|
||
| ## Using the Application | ||
|
|
||
| You'll need a Java 21 runtime installed to run the cli. | ||
| The current build targets **Java 25**, so you need a Java 25 (or newer) runtime to run a jar built | ||
| from this source. (Older released jars were built against older Java versions.) | ||
|
|
||
| To run the application: | ||
|
|
||
| ``` | ||
| java -jar onebusaway-gtfs-merge-cli.jar [--args] input_gtfs_path_a input_gtfs_path_b ... output_gtfs_path | ||
| java -jar onebusaway-gtfs-merge-cli.jar [options] input_gtfs_path_a input_gtfs_path_b ... output_gtfs_path | ||
| ``` | ||
|
|
||
| **Note**: Merging large GTFS feeds is often processor and memory intensive. You'll likely need to increase the | ||
| max amount of memory allocated to Java with an option like `-Xmx1G` (adjust the limit as needed). I also recommend | ||
| adding the `-server` argument if you are running the Oracle or OpenJDK, as it can really increase performance. | ||
| The **last** positional argument is the output path; every argument before it is an input feed. Each | ||
| path may be a directory containing a GTFS feed or a `.zip` file. At least one input and one output are | ||
| required (i.e. two positional arguments minimum). | ||
|
|
||
| ## Configuring the Application | ||
| **Note**: Merging large GTFS feeds can be processor- and memory-intensive. You may need to raise the | ||
| JVM heap limit with an option like `-Xmx1G` (adjust as needed). | ||
|
|
||
| The merge application supports a number of options and arguments for configuring the application's behavior. The | ||
| general pattern is to specify options for each type of file in a GTFS feed using the `--file` option, specifying | ||
| specific options for each file type after the `--file` option. Here's a quick example: | ||
| ### How feeds are combined | ||
|
|
||
| Input feeds are processed in **reverse command-line order** — entities from the *last* feed listed are | ||
| added to the output first, and entities from earlier feeds are merged in afterward. When an entity | ||
| from an earlier feed collides with one already in the output and is *not* treated as a duplicate (see | ||
| below), its id is automatically renamed by prefixing it (e.g. `a-`, `b-`, …), and all references to it | ||
| are rewritten. This automatic renaming replaces the old `--renameDuplicates` flag, which no longer | ||
| exists. | ||
|
|
||
|
Comment on lines
+34
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Missing documentation for the new This section documents the automatic context-prefix renaming and the 📝 Suggested addition Input feeds are processed in **reverse command-line order** — entities from the *last* feed listed are
added to the output first, and entities from earlier feeds are merged in afterward. When an entity
from an earlier feed collides with one already in the output and is *not* treated as a duplicate (see
below), its id is automatically renamed by prefixing it (e.g. `a-`, `b-`, …), and all references to it
are rewritten. This automatic renaming replaces the old `--renameDuplicates` flag, which no longer
exists.
+
+By default, colliding ids are renamed using the context prefix above. You can instead rename them
+using the owning agency's id with `--duplicateRenaming=agency`, paired by position with `--file` just
+like `--duplicateDetection` (see below). `--duplicateRenaming=context` restores the default explicitly.
+Values are case-insensitive.As per the PR objectives, "It exposes the Also applies to: 43-70 🤖 Prompt for AI Agents |
||
| ## Configuring per-file behavior | ||
|
|
||
| Merge behavior is configured per GTFS file with the `--file` option, followed by options that apply to | ||
| that file. Options are matched to files **by position**: the *N*-th `--file` is paired with the *N*-th | ||
| `--duplicateDetection`. For example: | ||
|
|
||
| ``` | ||
| --file=routes.txt --duplicateDetection=identity --file=calendar.txt --logDroppedDuplicates ... | ||
| --file=routes.txt --duplicateDetection=fuzzy --file=calendar.txt --duplicateDetection=none | ||
| ``` | ||
|
|
||
| The merge application supports merging the following files: | ||
| `--file` takes a real GTFS file name; it is resolved to an entity type through the GTFS schema, and an | ||
| unrecognized name causes the application to exit with an error. Recognized files include: | ||
|
|
||
| - `agency.txt` | ||
| - `stops.txt` | ||
| - `routes.txt` | ||
| - `trips.txt` and `stop_times.txt` | ||
| - `calendar.txt` and `calendar_dates.txt` | ||
| - `calendar.txt` and `calendar_dates.txt` | ||
| - `shapes.txt` | ||
| - `fare_attributes.txt` | ||
| - `fare_rules.txt` | ||
| - `frequencies.txt` | ||
| - `transfers.txt` | ||
|
|
||
| You can specify merge options for each of these files using the `--file=gtfs_file.txt` option. File types listed | ||
| together (eg. `trips.txt` and `stop_times.txt`) are handled by the same merge strategy, so specifying options for | ||
| either will have the same effect. For details on options you might specify, read on. | ||
| - `fare_attributes.txt` | ||
| - `fare_rules.txt` | ||
| - `feed_info.txt` | ||
|
Comment on lines
56
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find all CsvFields filename declarations to compare against the documented list
rg -n 'filename\s*=\s*"' --type java -g '!**/test/**'Repository: OneBusAway/onebusaway-gtfs-modules Length of output: 172 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate likely GTFS merger/doc sources and map their structure.
git ls-files | rg '(^|/)(docs/onebusaway-gtfs-merge-cli\.md|.*GtfsMerger.*|.*GtfsEntitySchemaFactory.*|.*entityClassesByFilename.*|.*MergeStrategy.*)$'
printf '\n## Outline: docs/onebusaway-gtfs-merge-cli.md\n'
wc -l docs/onebusaway-gtfs-merge-cli.md
sed -n '1,220p' docs/onebusaway-gtfs-merge-cli.md
printf '\n## Search: filenames / recognized files / merge strategies\n'
rg -n 'entityClassesByFilename|getEntityClasses|Recognized files|merge strategy|MergeStrategy|filename' src test . -g '!**/target/**' -g '!**/build/**'Repository: OneBusAway/onebusaway-gtfs-modules Length of output: 45058 🏁 Script executed: #!/bin/bash
set -euo pipefail
# If the search above was too broad, narrow down by likely package names.
fd -a 'GtfsMergerMain.*|GtfsMerger.*|GtfsEntitySchemaFactory.*' .Repository: OneBusAway/onebusaway-gtfs-modules Length of output: 812 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '## GtfsEntitySchemaFactory\n'
sed -n '1,220p' onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactory.java
printf '\n## GtfsMergerMain\n'
sed -n '1,260p' onebusaway-gtfs-merge-cli/src/main/java/org/onebusaway/gtfs_merge/GtfsMergerMain.java
printf '\n## GtfsMerger\n'
sed -n '1,320p' onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/GtfsMerger.javaRepository: OneBusAway/onebusaway-gtfs-modules Length of output: 20324 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '## GtfsEntitySchemaFactoryTest\n'
sed -n '1,220p' onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactoryTest.java
printf '\n## GtfsMergerMainTest\n'
sed -n '1,260p' onebusaway-gtfs-merge-cli/src/test/java/org/onebusaway/gtfs_merge/GtfsMergerMainTest.java
printf '\n## GtfsMergerTest relevant strategy assertions\n'
sed -n '250,380p' onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/GtfsMergerTest.javaRepository: OneBusAway/onebusaway-gtfs-modules Length of output: 13058 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '## Merge strategy entity-type mappings\n'
rg -n 'getEntityTypes|new .*MergeStrategy|extends Abstract' onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/strategies -g '!**/target/**'
printf '\n## TripMergeStrategy\n'
sed -n '1,220p' onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/strategies/TripMergeStrategy.java
printf '\n## ServiceCalendarMergeStrategy\n'
sed -n '1,220p' onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/strategies/ServiceCalendarMergeStrategy.java
printf '\n## AreaMergeStrategy\n'
sed -n '1,160p' onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/strategies/AreaMergeStrategy.javaRepository: OneBusAway/onebusaway-gtfs-modules Length of output: 14539 Add 🤖 Prompt for AI Agents |
||
|
|
||
| Files listed together (e.g. `trips.txt` and `stop_times.txt`) are handled by the same merge strategy, | ||
| so naming either configures both. | ||
|
|
||
| ## Handling Duplicates | ||
|
|
||
| The main issue to consider when merging GTFS feeds is the handling of duplicate entries between the two feeds, | ||
| including how to identify duplicates and what to do with duplicates when they are found. | ||
| The main issue when merging GTFS feeds is handling duplicate entries between feeds: how to identify | ||
| duplicates, and what to do when one is found. | ||
|
|
||
| ### Identifying Duplicates | ||
|
|
||
| We support a couple of methods for determining when entries from two different feeds are actually duplicates. By default, | ||
| the merge tool will attempt to automatically determine the best merge strategy to use. You can also control the specific | ||
| strategy used on a per-file basis using the `--duplicateDetection` argument. You can specify any of the following | ||
| strategies for duplicate detection. | ||
|
|
||
| - `--duplicateDetection=identity`: If two entries have the same id (eg. stop id, route id, trip id), then they are | ||
| considered the same. This is the more strict matching policy. | ||
|
|
||
| - `--duplicateDetection=fuzzy`: If two entries have common elements (eg. stop name or location, route short name, | ||
| trip stop sequence), then they are considered the same. This is the more lenient matching policy, and is highly | ||
| dependent on the type of GTFS entry being matched. | ||
| - `--duplicateDetection=none`: Entries between two feeds are never considered to be duplicates, even if they have | ||
| the same id or similar properties. | ||
| By default, each file's merge strategy automatically picks the duplicate-detection approach it | ||
| considers best for that entity type. You can override this per file with `--duplicateDetection`: | ||
|
|
||
| - `--duplicateDetection=identity`: two entries are the same if they share an id (e.g. stop id, route | ||
| id, trip id). This is the stricter policy. | ||
|
|
||
| - `--duplicateDetection=fuzzy`: two entries are the same if they share defining properties (e.g. stop | ||
| name/location, route short name, trip stop sequence). This is the more lenient policy and is highly | ||
| dependent on the entity type. | ||
|
|
||
| - `--duplicateDetection=none`: entries are never considered duplicates, even with the same id or | ||
| similar properties. Colliding ids are renamed instead of dropped (see "How feeds are combined"). | ||
|
|
||
| Values are case-insensitive. | ||
|
|
||
| ### Logging Duplicates | ||
|
|
||
| Sometimes your feed might have unexpected duplicates. You can tell the merge tool to log duplicates it finds or even | ||
| immediately exit with the following arguments: | ||
| You can ask the tool to report or reject duplicates it drops: | ||
|
|
||
| - `--logDroppedDuplicates` - log a message when a duplicate is found | ||
|
|
||
| - `--errorOnDroppedDuplicates` - throw an exception when a duplicate is found, stopping the program | ||
|
|
||
| ## Examples | ||
| - `--logDroppedDuplicates` — log a warning when a duplicate is dropped. | ||
|
|
||
| ### Handling a Service Change | ||
| - `--errorOnDroppedDuplicates` — throw an exception and stop when a duplicate is dropped. | ||
|
|
||
| Agencies often schedule major changes to their system around a particular date, with one GTFS feed for before the | ||
| service change and a different GTFS feed for after. We'd like to be able to merge these disjoint feeds into one | ||
| feed with continuous coverage. | ||
| **Important:** these two flags are applied *inside* the per-`--file` configuration loop, so they only | ||
| take effect for files you have also named with `--file`. Passing at least one `--file` is required for | ||
| them to do anything (and, in the current code, for the merge to run without error). | ||
|
|
||
| In our example, an agency produces two feeds where the entries in `agency.txt` and `stops.txt` are exactly | ||
| the same, so the default policy of identifying and dropping duplicates will work fine there. The `routes.txt` file | ||
| is a bit trickier, since the route ids are different between the two feeds but the entries are largely the same. We | ||
| will use fuzzy duplicate detection to match the routes between the two feeds. | ||
| ### Other options | ||
|
|
||
| The next issue is the `calendar.txt` file. The agency uses the same `service_id` values in both feeds | ||
| (eg. `WEEK`, `SAT`, `SUN`) with different start and end dates in the two feeds. If the default policy of | ||
| dropping duplicate entries was used, we'd lose the dates in one of the service periods. Instead, we rename duplicates | ||
| such that the service ids from the second feed will be renamed to `b-WEEK`, `b-SAT`, etc. and all | ||
| `trips.txt` entries in the second feed will be updated appropriately. The result is that trips from the first | ||
| and second feed will both have the proper calendar entries in the merged feed. | ||
| - `--debug` — print the resolved merge strategies before the merge begins. | ||
| - `--help`, `--version` — standard help/version output. | ||
|
|
||
| Putting it all together, here is what the command-line options for the application would look like: | ||
| ## Example: handling a service change | ||
|
|
||
| Agencies often schedule major changes around a particular date, publishing one feed for before the | ||
| change and another for after, and we want to merge them into a single feed with continuous coverage. | ||
|
|
||
| Suppose `agency.txt` and `stops.txt` are identical across the two feeds, so the default duplicate | ||
| handling drops the duplicates correctly. The `routes.txt` entries describe the same routes but use | ||
| different route ids, so we use fuzzy detection to match them. The `calendar.txt` file reuses the same | ||
| `service_id` values (e.g. `WEEK`, `SAT`, `SUN`) with different start/end dates in each feed — these are | ||
| *not* duplicates, so we force `none` detection, which makes the merger rename the colliding service | ||
| ids (e.g. to `a-WEEK`) and rewrite the affected `trips.txt` references. Both feeds' trips then keep | ||
| their correct calendars in the merged output. | ||
|
|
||
| Putting it together — with `earlier.zip` first and `later.zip` second so the later feed's ids win: | ||
|
|
||
| ``` | ||
| java -jar onebusaway-gtfs-merge-cli.jar \ | ||
| --file=routes.txt --duplicateDetection=fuzzy \ | ||
| --file=calendar.txt --duplicateDetection=none \ | ||
| earlier.zip later.zip merged.zip | ||
| ``` | ||
| --file=routes.txt --fuzzyDuplicates --file=calendar.txt --renameDuplicates | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language identifier to fenced code blocks.
markdownlint flags these three blocks (MD040) for missing a language.
📝 Proposed fix
Apply the same fix at lines 49 and 125.
Also applies to: 49-49, 125-125
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 23-23: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Source: Linters/SAST tools