{"id":13425886,"url":"https://github.com/public-transport/gtfs-via-postgres","last_synced_at":"2026-02-12T12:13:45.464Z","repository":{"id":38381260,"uuid":"257130655","full_name":"public-transport/gtfs-via-postgres","owner":"public-transport","description":"Process GTFS Static/Schedule by importing it into a PostgreSQL database.","archived":false,"fork":false,"pushed_at":"2025-12-10T11:51:54.000Z","size":866,"stargazers_count":122,"open_issues_count":30,"forks_count":21,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-12-10T16:38:23.050Z","etag":null,"topics":["gtfs","gtfs-schedule","gtfs-static","postgres","postgresql","public-transport","sql","transit"],"latest_commit_sha":null,"homepage":"https://github.com/derhuerst/gtfs-via-postgres#gtfs-via-postgres","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/public-transport.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":".github/funding.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"liberapay":"derhuerst","patreon":"derhuerst","github":"derhuerst","open_collective":"public-transport-oss","ko_fi":"derhuerst"}},"created_at":"2020-04-20T00:21:55.000Z","updated_at":"2025-11-26T02:13:28.000Z","dependencies_parsed_at":"2023-01-29T00:25:12.393Z","dependency_job_id":"74b9be16-6b8a-4f9d-8bc7-7ea8c4ec2688","html_url":"https://github.com/public-transport/gtfs-via-postgres","commit_stats":{"total_commits":270,"total_committers":7,"mean_commits":38.57142857142857,"dds":"0.033333333333333326","last_synced_commit":"5321ae0d2489e2ebfb799859ab6167fda43d7bc1"},"previous_names":["derhuerst/gtfs-via-postgres"],"tags_count":66,"template":false,"template_full_name":null,"purl":"pkg:github/public-transport/gtfs-via-postgres","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fgtfs-via-postgres","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fgtfs-via-postgres/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fgtfs-via-postgres/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fgtfs-via-postgres/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/public-transport","download_url":"https://codeload.github.com/public-transport/gtfs-via-postgres/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fgtfs-via-postgres/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29365812,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-12T08:51:36.827Z","status":"ssl_error","status_checked_at":"2026-02-12T08:51:26.849Z","response_time":55,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gtfs","gtfs-schedule","gtfs-static","postgres","postgresql","public-transport","sql","transit"],"created_at":"2024-07-31T00:01:21.263Z","updated_at":"2026-02-12T12:13:45.456Z","avatar_url":"https://github.com/public-transport.png","language":"JavaScript","funding_links":["https://liberapay.com/derhuerst","https://patreon.com/derhuerst","https://github.com/sponsors/derhuerst","https://opencollective.com/public-transport-oss","https://ko-fi.com/derhuerst"],"categories":["JavaScript"],"sub_categories":[],"readme":"# gtfs-via-postgres\n\n**Import [GTFS Static/Schedule](https://gtfs.org/documentation/schedule/reference/) datasets into a [PostgreSQL database](https://www.postgresql.org)**, to allow for efficient querying and analysis.\n\n[![npm version](https://img.shields.io/npm/v/gtfs-via-postgres.svg)](https://www.npmjs.com/package/gtfs-via-postgres)\n[![binary build status](https://img.shields.io/github/actions/workflow/status/public-transport/gtfs-via-postgres/publish.yml?label=binary%20build)](https://github.com/public-transport/gtfs-via-postgres/actions)\n[![Prosperity/Apache license](https://img.shields.io/static/v1?label=license\u0026message=Prosperity%2FApache\u0026color=0997E8)](#license)\n![minimum Node.js version](https://img.shields.io/node/v/gtfs-via-postgres.svg)\n[![support me via GitHub Sponsors](https://img.shields.io/badge/support%20me-donate-fa7664.svg)](https://github.com/sponsors/derhuerst)\n[![chat with me on Twitter](https://img.shields.io/badge/chat%20with%20me-on%20Twitter-1da1f2.svg)](https://twitter.com/derhuerst)\n\n- ✅ handles [daylight saving time correctly](#correctness-vs-speed-regarding-gtfs-time-values) but retains reasonable lookup performance\n- ✅ supports `frequencies.txt`\n- ✨ joins `stop_times.txt`/`frequencies.txt`, `calendar.txt`/`calendar_dates.txt`, `trips.txt`, `route.txt` \u0026 `stops.txt` into [views](https://www.postgresql.org/docs/14/sql-createview.html) for straightforward data analysis (see below)\n- 🚀 is carefully optimised to let PostgreSQL's query planner do its magic, yielding quick lookups even with large datasets (see [performance section](#performance))\n- ✅ validates and imports `translations.txt`\n- ✨ exposes (almost) all data via GraphQL using [PostGraphile](https://www.graphile.org/postgraphile/introduction/), and as a RESTful API using [PostgREST](https://postgrest.org/)\n\nTo work with the time-related data (`stop_times` etc.), `gtfs-via-postgres` supports two \"mental models\":\n\n- the time-*unexpanded* data that is almost directly taken from the GTFS Schedule data – This is useful if you want to do network analysis.\n- the time-*expanded* view that \"applies\" every trip's `stop_times` rows to all of its service days – This is useful for routing \u0026 queries from the traveller's perspective.\n\n\n## Installation\n\n```shell\nnpm install -g gtfs-via-postgres\n```\n\nOr use [`npx`](https://npmjs.com/package/npx). ✨\n\nThere are also [prebuilt binaries](https://github.com/public-transport/gtfs-via-postgres/releases/latest) and [Docker images](https://github.com/public-transport/gtfs-via-postgres/pkgs/container/gtfs-via-postgres) available.\n\n*Note:* `gtfs-via-postgres` **needs PostgreSQL \u003e=14** to work, as it uses the [`WITH … AS NOT MATERIALIZED`](https://www.postgresql.org/docs/14/queries-with.html#id-1.5.6.12.7) syntax. You can check your PostgreSQL server's version with `psql -t -c 'SELECT version()'`.\n\n\n## Getting Started\n\nIf you have a `.zip` GTFS feed, unzip it into individual files.\n\nWe're going to use the [2022-07-01 *VBB* feed](https://vbb-gtfs.jannisr.de/2022-07-01/) as an example, which consists of individual files already.\n\n```sh\nwget --compression auto \\\n    -r --no-parent --no-directories -R .csv.gz \\\n    -P gtfs -N 'https://vbb-gtfs.jannisr.de/2022-07-01/'\n# …\n# Downloaded 14 files in 20s.\nls -lh gtfs\n# 3.3K agency.csv\n#  97K calendar.csv\n# 1.1M calendar_dates.csv\n# 2.5K datapackage.json\n#  64B frequencies.csv\n# 5.9K levels.csv\n# 246B license\n# 8.3M pathways.csv\n#  49K routes.csv\n# 146M shapes.csv\n# 368M stop_times.csv\n# 5.0M stops.csv\n# 4.7M transfers.csv\n#  16M trips.csv\n```\n\nDepending on your specific setup, configure access to the PostgreSQL database via [`PG*` environment variables](https://www.postgresql.org/docs/14/libpq-envars.html):\n\n```sh\nexport PGUSER=postgres\nexport PGPASSWORD=password\nenv PGDATABASE=postgres psql -c 'create database vbb_2022_02_25'\nexport PGDATABASE=vbb_2022_02_25\n```\n\n*Note*: `gtfs-via-postgres` generates SQL that contains the `CREATE EXTENSION postgis` instruction. For this to work, the PostgreSQL user you're connecting as needs the `CREATE` [permission](https://www.postgresql.org/docs/14/ddl-priv.html) on the database. Also, the `postgis` extension must either be marked as trusted (by putting `trusted = true` into `$(pg_config --sharedir)/extension/postgis.control`), or your user must be a superuser.\n\nInstall `gtfs-via-postgres` and use it to import the GTFS data:\n\n```sh\nnpm install -D gtfs-via-postgres\nnpm exec -- gtfs-to-sql --require-dependencies -- gtfs/*.csv | sponge | psql -b\n# agency\n# calendar\n# CREATE EXTENSION\n# BEGIN\n# CREATE TABLE\n# COPY 37\n# …\n# CREATE INDEX\n# CREATE VIEW\n# COMMIT\n```\n\nImporting will take 10s to 15m, depending on the size of the feed. On an [M2 MacBook Air](https://support.apple.com/en-us/111867), importing the above feed takes about 9m; Importing the [260kb 2021-10-06 Amtrak feed](https://transitfeeds.com/p/amtrak/1136/20211006) takes 6s.\n\nThe [DELFI](https://www.delfi.de/) feed with ca. 330 MB may take 1 hour or more and require at least 35 GB of disk space for the database.\n\nIn addition to a table for each GTFS file, `gtfs-via-postgres` adds these views to help with real-world analysis:\n\n- `service_days` ([materialized](https://www.postgresql.org/docs/14/sql-creatematerializedview.html)) \"applies\" [`calendar_dates`](https://gtfs.org/documentation/schedule/reference/#calendar_datestxt) to [`calendar`](https://gtfs.org/documentation/schedule/reference/#calendartxt) to give you all days of operation for each \"service\" defined in [`calendar`](https://gtfs.org/documentation/schedule/reference/#calendartxt).\n- `arrivals_departures` \"applies\" [`stop_times`](https://gtfs.org/documentation/schedule/reference/#stop_timestxt)/[`frequencies`](https://gtfs.org/documentation/schedule/reference/#frequenciestxt) to [`trips`](https://gtfs.org/documentation/schedule/reference/#tripstxt) and `service_days` to give you all arrivals/departures at each stop with their *absolute* dates \u0026 times. It also resolves each stop's parent station ID \u0026 name.\n- `connections` \"applies\" [`stop_times`](https://gtfs.org/documentation/schedule/reference/#stop_timestxt)/[`frequencies`](https://gtfs.org/documentation/schedule/reference/#frequenciestxt) to [`trips`](https://gtfs.org/documentation/schedule/reference/#tripstxt) and `service_days`, just like `arrivals_departures`, but gives you departure (at stop A) \u0026 arrival (at stop B) *pairs*.\n- `shapes_aggregated` aggregates individual shape points in [`shapes`](https://gtfs.org/documentation/schedule/reference/#shapestxt) into a [PostGIS `LineString`](http://postgis.net/workshops/postgis-intro/geometries.html#linestrings).\n- `stats_by_route_date` provides the number of arrivals/departures by route ID and date. – [read more](docs/analysis/feed-by-route-date.md)\n- `stats_by_agency_route_stop_hour` provides the number of arrivals/departures by agency ID, route ID, stop ID \u0026 hour. – [read more](docs/analysis/feed-by-agency-route-stop-and-hour.md)\n- In contrast to `stats_by_route_date` \u0026 `stats_by_agency_route_stop_hour`, `stats_active_trips_by_hour` provides the number of *currently running* trips for each hour in the feeds period of time.\n\nAs an example, we're going to use the `arrivals_departures` view to query all *absolute* departures at `de:11000:900120003` (*S Ostkreuz Bhf (Berlin)*) between `2022-03-23T12:30+01` and  `2022-03-23T12:35+01`:\n\n```sql\nSELECT *\nFROM arrivals_departures\nWHERE station_id = 'de:11000:900120003'\nAND t_departure \u003e= '2022-03-23T12:30+01' AND t_departure \u003c= '2022-03-23T12:35+01'\n```\n\n`route_id` | `route_short_name` | `route_type` | `trip_id` | `date` | `stop_sequence` | `t_arrival` | `t_departure` | `stop_id` | `stop_name` | `station_id` | `station_name`\n-|-|-|-|-|-|-|-|-|-|-|-\n`10148_109` | `S3` | `109` | `169035756` | `2022-03-23 00:00:00` | `19` | `2022-03-23 12:31:24+01` | `2022-03-23 12:32:12+01` | `de:11000:900120003:2:53` | `S Ostkreuz Bhf (Berlin)` | `de:11000:900120003` | `S Ostkreuz Bhf (Berlin)`\n`10148_109` | `S3` | `109` | `169035899` | `2022-03-23 00:00:00` | `10` | `2022-03-23 12:33:06+01` | `2022-03-23 12:33:54+01` | `de:11000:900120003:3:55` | `S Ostkreuz Bhf (Berlin)` | `de:11000:900120003` | `S Ostkreuz Bhf (Berlin)`\n`10162_109` | `S7` | `109` | `169128381` | `2022-03-23 00:00:00` | `19` | `2022-03-23 12:33:54+01` | `2022-03-23 12:34:42+01` | `de:11000:900120003:2:53` | `S Ostkreuz Bhf (Berlin)` | `de:11000:900120003` | `S Ostkreuz Bhf (Berlin)`\n`10162_109` | `S7` | `109` | `169128495` | `2022-03-23 00:00:00` | `9` | `2022-03-23 12:30:36+01` | `2022-03-23 12:31:24+01` | `de:11000:900120003:3:55` | `S Ostkreuz Bhf (Berlin)` | `de:11000:900120003` | `S Ostkreuz Bhf (Berlin)`\n`10223_109` | `S41` | `109` | `169054370` | `2022-03-23 00:00:00` | `21` | `2022-03-23 12:30:24+01` | `2022-03-23 12:31:12+01` | `de:11000:900120003:5:58` | `S Ostkreuz Bhf (Berlin)` | `de:11000:900120003` | `S Ostkreuz Bhf (Berlin)`\n`10227_109` | `S42` | `109` | `169071882` | `2022-03-23 00:00:00` | `6` | `2022-03-23 12:30:30+01` | `2022-03-23 12:31:12+01` | `de:11000:900120003:5:59` | `S Ostkreuz Bhf (Berlin)` | `de:11000:900120003` | `S Ostkreuz Bhf (Berlin)`\n`19040_100` | `RB14` | `100` | `178748721` | `2022-03-23 00:00:00` | `13` | `2022-03-23 12:30:00+01` | `2022-03-23 12:30:00+01` | `de:11000:900120003:1:50` | `S Ostkreuz Bhf (Berlin)` | `de:11000:900120003` | `S Ostkreuz Bhf (Berlin)`\n`22664_2` | `FEX` | `2` | `178748125` | `2022-03-23 00:00:00` | `1` | `2022-03-23 12:32:00+01` | `2022-03-23 12:34:00+01` | `de:11000:900120003:4:57` | `S Ostkreuz Bhf (Berlin)` | `de:11000:900120003` | `S Ostkreuz Bhf (Berlin)`\n\n### translations\n\nThere are some `…_translated` views (e.g. `stops_translated`, `arrivals_departures_translated`) that\n- join their respective source table with `translations`, so that each (translatable) field is translated in every provided language,\n- add a `…_lang` column for each translated column (e.g. `stop_name_lang` for `stop_name`) that indicates the language of the translation.\n\nAssuming a dataset with `translations.csv`, let's query all stops with a `de-CE` translation, falling back to the untranslated values:\n\n```sql\nSELECT\n    stop_id,\n    stop_name, stop_name_lang,\n    stop_url,\nFROM stops_translated\nWHERE (stop_name_lang = 'de-CH' OR stop_name_lang IS NULL)\nAND (stop_url_lang = 'de-CH' OR stop_url_lang IS NULL)\n```\n\n\n## Usage\n\n```\nUsage:\n    gtfs-to-sql [options] [--] \u003cgtfs-file\u003e ...\nOptions:\n    --silent                  -s  Don't show files being converted.\n    --require-dependencies    -d  Require files that the specified GTFS files depend\n                                  on to be specified as well (e.g. stop_times.txt\n                                  requires trips.txt). Default: false\n    --ignore-unsupported      -u  Ignore unsupported files. Default: false\n    --route-types-scheme          Set of route_type values to support.\n                                    - basic: core route types in the GTFS spec\n                                    - google-extended: Extended GTFS Route Types [1]\n                                    - tpeg-pti: proposed TPEG-PTI-based route types [2]\n                                    May also be a set of these schemes, separated by `,`.\n                                    Default: google-extended\n    --trips-without-shape-id      Don't require trips.txt items to have a shape_id.\n                                    Default if shapes.txt has not been provided.\n    --routes-without-agency-id    Don't require routes.txt items to have an agency_id.\n    --stops-without-level-id      Don't require stops.txt items to have a level_id.\n                                    Default if levels.txt has not been provided.\n    --stops-location-index        Create a spatial index on stops.stop_loc for efficient\n                                    queries by geolocation.\n    --lower-case-lang-codes       Accept Language Codes (e.g. in feed_info.feed_lang)\n                                    with a different casing than the official BCP-47\n                                    language tags (as specified by the GTFS spec),\n                                    by lower-casing all of them before validating.\n                                    http://www.rfc-editor.org/rfc/bcp/bcp47.txt\n                                    http://www.w3.org/International/articles/language-tags/\n    --stats-by-route-date         Wether to generate a stats_by_route_date view\n                                    letting you analyze all data per routes and/or date:\n                                    - none: Don't generate a view.\n                                    - view: Fast generation, slow access.\n                                    - materialized-view: Slow generation, fast access.\n                                    Default: none\n    --stats-by-agency-route-stop-hour\n                                  Generate a view letting you analyze arrivals/\n                                    departures per route, stop and hour.\n                                    The flag works like --stats-by-route-date.\n    --stats-active-trips-by-hour  Generate a view letting you analyze the number of\n                                    currently running trips over time, by hour.\n                                    Like --stats-by-route-date, this flag accepts\n                                    none, view \u0026 materialized-view.\n    --schema                      The schema to use for the database. Default: public\n                                    Even when importing into a schema other than `public`,\n                                    a function `public.gtfs_via_postgres_import_version()`\n                                    gets created, to ensure that multiple imports into the\n                                    same database are all made using the same version. See\n                                    also multiple-datasets.md in the docs.\n    --postgraphile                Tweak generated SQL for PostGraphile usage.\n                                    https://www.graphile.org/postgraphile/\n    --postgraphile-password       Password for the PostGraphile PostgreSQL user.\n                                    Default: $POSTGRAPHILE_PGPASSWORD, fallback random.\n    --postgrest                   Tweak generated SQL for PostgREST usage.\n                                    Please combine it with --schema.\n                                    https://postgrest.org/\n    --postgrest-password          Password for the PostgREST PostgreSQL user `web_anon`.\n                                    Default: $POSTGREST_PGPASSWORD, fallback random.\n    --postgrest-query-cost-limit  Define a cost limit [1] for queries executed by PostgREST\n                                    on behalf of a user. It is only enforced if\n                                    pg_plan_filter [2] is installed in the database!\n                                    Must be a positive float. Default: none\n                                    [1] https://www.postgresql.org/docs/14/using-explain.html\n                                    [2] https://github.com/pgexperts/pg_plan_filter\n    --import-metadata             Create functions returning import metadata:\n                                    - gtfs_data_imported_at (timestamp with time zone)\n                                    - gtfs_via_postgres_version (text)\n                                    - gtfs_via_postgres_options (jsonb)\nExamples:\n    gtfs-to-sql some-gtfs/*.txt | sponge | psql -b # import into PostgreSQL\n    gtfs-to-sql -u -- some-gtfs/*.txt | gzip \u003egtfs.sql.gz # generate a gzipped SQL dump\n\n[1] https://developers.google.com/transit/gtfs/reference/extended-route-types\n[2] https://groups.google.com/g/gtfs-changes/c/keT5rTPS7Y0/m/71uMz2l6ke0J\n```\n\nSome notable limitations mentioned in the [PostgreSQL 14 documentation on date/time types](https://www.postgresql.org/docs/14/datatype-datetime.html):\n\n\u003e For `timestamp with time zone`, the internally stored value is always in UTC (Universal Coordinated Time, traditionally known as Greenwich Mean Time, GMT). An input value that has an explicit time zone specified is converted to UTC using the appropriate offset for that time zone.\n\n\u003e When a `timestamp with time zone` value is output, it is always converted from UTC to the current `timezone` zone, and displayed as local time in that zone. To see the time in another time zone, either change `timezone` or use the `AT TIME ZONE` construct […].\n\nYou can run queries with date+time values in any timezone (offset) and they will be processed correctly, but the output will always be in the database timezone (offset), unless you have explicitly used `AT TIME ZONE`.\n\n### With Docker\n\n*Note:* Just like the `npm`-installed variant, the Docker integration too assumes that your GTFS dataset consists of individual files (i.e. unzipped).\n\nInstead of installing via `npm`, you can use [the `ghcr.io/public-transport/gtfs-via-postgres` Docker image](https://github.com/public-transport/gtfs-via-postgres/pkgs/container/gtfs-via-postgres):\n\n```shell\n# variant A: use Docker image just to convert GTFS to SQL\ndocker run --rm --volume /path/to/gtfs:/gtfs \\\n\tghcr.io/public-transport/gtfs-via-postgres --require-dependencies -- '/gtfs/*.csv' \\\n    | sponge | psql -b\n```\n\n*Note:* Remember to pass the `/gtfs/*.csv` glob as a string (with `'`), so that it gets evaluated *inside* the Docker container.\n\nWith the code above, the `psql -b` process will run *outside* of the Docker container, so your host machine needs access to PostgreSQL.\n\nIf you want to directly import the GTFS data *from within the Docker container*, you need add `psql` to the image and run it from inside. To do that, write a new Dockerfile that extends the `ghcr.io/public-transport/gtfs-via-postgres` image:\n\n```Dockerfile\nFROM ghcr.io/public-transport/gtfs-via-postgres\nENV PGPORT=5432 PGUSER=postgres\nWORKDIR /gtfs\n# pass all arguments into gtfs-via-postgres, pipe output into psql:\nENTRYPOINT [\"/bin/sh\", \"-c\", \"gtfs-via-postgres $0 $@ | sponge | psql -b\"]\n```\n\n```shell\n# start PostgreSQL DB in another container \"db\"\ndocker run --name db -p 5432:5432 -e POSTGRES_PASSWORD=password postgis/postgis\n\n# variant B: use Docker image to convert GTFS to SQL and import it directly\ndocker build -t import-gtfs . # build helper Docker image from Dockerfile\ndocker run --rm --volume /path/to/gtfs:/gtfs \\\n\t--link db -e PGHOST=db -e PGPASSWORD=password \\\n\timport-gtfs --require-dependencies -- '/gtfs/*.csv'\n```\n\n### Importing a GTFS Schedule feed continuously\n\n[postgis-gtfs-importer](https://github.com/mobidata-bw/postgis-gtfs-importer) imports [GTFS Schedule](https://gtfs.org/schedule/) data into a [PostGIS](https://postgis.net) database using `gtfs-via-postgres`. It allows running a production service (e.g. an API) on top of programmatically re-imported data from a periodically changing GTFS feed without downtime.\n\nBecause it works as [atomically](https://en.wikipedia.org/wiki/Atomicity_(database_systems)) as possible with PostgreSQL, it makes the import pipeline *robust*, even if an import fails or if simultaneous imports get started.\n\n### Exporting data efficiently\n\nIf you want to export data from the database, use the [`COPY` command](https://www.postgresql.org/docs/14/sql-copy.html); On an [M1 MacBook Air](https://en.wikipedia.org/wiki/MacBook_Air_(Apple_silicon)#Third_generation_(Retina_with_Apple_silicon)), PostgreSQL 14 can export about 500k `connections` rows per second.\n\n```shell\npsql -c 'COPY (SELECT * FROM connections) TO STDOUT csv HEADER' \u003econnections.csv\n```\n\nIn the nested `SELECT` query, you can use features like `WHERE`, `ORDER BY` and `LIMIT`. Because `psql` passes on the exported data right away, you could stream it into another process.\n\n### Querying stops by location efficiently\n\nIf you want to find stops by (geo)location, run `gtfs-via-postgres` with `--stops-location-index`. This will create a [spatial index](https://postgis.net/workshops/postgis-intro/indexing.html) on `stops.stop_loc`, so that most [PostGIS functions \u0026 operators](https://postgis.net/docs/manual-3.2/reference.html#Measurement_Functions) make use of it.\n\n### GraphQL support\n\nThe `--postgraphile` flag changes the SQL generated by `gtfs-via-postgres` slightly, so that you get a reasonably idiomatic GraphQL API out-of-the-box when running [PostGraphile](https://www.graphile.org/postgraphile/) v4 on it:\n\n```shell\n# import data into PostgreSQL with PostGraphile tweaks\nnpm exec -- gtfs-to-sql -d --postgraphile -- gtfs/*.csv | sponge | psql -b\n```\n\nIn line with the intended PostGraphile usage, `gtfs-via-postgres` will create a PostgreSQL role/user `postgraphile` with read-only access to the DB. You can set the `postgraphile`'s password with the `--postgraphile-password` option, or using the `$POSTGRAPHILE_PGPASSWORD` environment variable; By default, it will use (and log) a random password.\n\n`gtfs-via-postgres` *doesn't* specify PostGraphile as a regular dependency, but as `peerDependencies`, in order to stay lightweight for users who don't need the GraphQL interface. Some versions of some package managers install unmet peer dependencies, some don't. Let's make sure that PostGraphile (and its plugins) are installed:\n\n```shell\nnpm install \\\n    postgraphile@^4.12 \\\n    @graphile-contrib/pg-simplify-inflector@^6.1 \\\n    @graphile/postgis@^0.2.0-0\n```\n\nThe `serve-gtfs-via-graphql` helper script configures and runs PostGraphile. With `NODE_ENV=development`, it will\n\n- serve a fully configured [GraphiQL UI](https://graphql-dotnet.github.io/docs/getting-started/graphiql/) at `/graphiql`\n- provide more errors on database \u0026 query errors\n- allow [using PostgreSQL's `EXPLAIN` via GraphQL](https://www.graphile.org/postgraphile/debugging/#via-postgraphiql-explain)\n\n```\n# listens on port 3000, this can be changed using $PORT\nenv NODE_ENV=development npm exec -- serve-gtfs-via-graphql\n```\n\n**As an example for the GraphQL API, check out the [test query](test/sample-gtfs-feed-postgraphile-test.graphql)** or open the [GraphiQL UI](https://github.com/graphql/graphiql) served at [`localhost:3000/graphiql`](http://localhost:3000/graphiql).\n\n### REST API support\n\nWith the `--postgrest` flag, `gtfs-via-postgres` will augment the schema with a `web_anon` role and some comments, so that when running [PostgREST](https://postgrest.org/) on the database, you will get a powerful REST API.\n\n[read more](docs/postgrest.md)\n\n### more guides\n\nThe [`docs` directory](docs) contains more instructions on how to use `gtfs-via-postgres`.\n\n\n## Correctness vs. Speed regarding GTFS Time Values\n\nWhen matching time values from `stop_times` against dates from `calendar`/`calendar_dates`, you have to take into account that **GTFS Time values can be \u003e24h and [are not relative to the beginning of the day but relative to noon - 12h](https://gist.github.com/derhuerst/574edc94981a21ef0ce90713f1cff7f6)**. ([There are a few libraries that don't do this.](https://github.com/r-transit/tidytransit/issues/175#issuecomment-979213277))\n\nThis means that, in order to determine all *absolute* points in time where a particular trip departs at a particular stop, you *cannot* just loop over all \"service dates\" and add the time value (as in `beginning_of_date + departure_time`); Instead, for each date, you have to determine noon, subtract 12h and then apply the time, which might extend arbitrarily far into the following days.\n\nLet's consider two examples:\n\n- A `departure_time` of `26:59:00` with a trip running on `2021-03-01`: The time, applied to this specific date, \"extends\" into the following day, so it actually departs at `2021-03-02T02:59+01`.\n- A departure time of `03:01:00` with a trip running on `2021-03-28`: This is when the standard -\u003e DST switch happens in the `Europe/Berlin` timezone. Because the dep. time refers to noon - 12h (*not* to midnight), it actually happens at `2021-03-28T03:01+02` which is *not* `3h1m` after `2021-03-28T00:00+01`.\n\n`gtfs-via-postgres` always prioritizes correctness over speed. Because it follows the GTFS semantics, when filtering `arrivals_departures` by *absolute* departure date+time, it cannot automatically filter `service_days` (which is `calendar` and `calendar_dates` combined), because **even a date *before* the date of the desired departure time frame might still end up *within*, when combined with a `departure_time` of e.g. `27:30:00`**; Instead, it has to consider all `service_days` and apply the `departure_time` to all of them to check if they're within the range.\n\nHowever, if you determine your feed's largest `arrival_time`/`departure_time`, you can filter on `date` when querying `arrivals_departures`; This allows PostgreSQL to reduce the number of joins and calendar calculations by orders of magnitude, speeding up your queries significantly. `gtfs-via-postgres` provides two low-level helper functions `largest_arrival_time()` \u0026 `largest_departure_time()` for this, as well as two high-level helper functions `dates_filter_min(t_min)` \u0026 `dates_filter_max(t_max)` (see below).\n\nFor example, when querying all *absolute* departures at `de:11000:900120003` (*S Ostkreuz Bhf (Berlin)*) between `2022-03-23T12:30+01` and  `2022-03-23T12:35+01` within the [2022-02-25 *VBB* feed](https://vbb-gtfs.jannisr.de/2022-02-25/), filtering by `date` speeds it up nicely (Apple M1, PostgreSQL 14.2):\n\n`station_id` filter | `date` filter | query time | nr of results\n-|-|-|-\n`de:11000:900120003` | *none* | 230ms | ~574k\n`de:11000:900120003` | `2022-03-13` \u003e= `date` \u003c `2022-04-08` | 105ms | ~51k\n`de:11000:900120003` | `2022-03-23` \u003e= `date` \u003c `2022-03-24` | 55ms | ~2k\n`de:11000:900120003` | `2022-03-22` \u003e `date` \u003c `2022-03-24` | 55ms | ~2k\n*none* | *none* | 192s | 370m\n*none* | `2022-03-13` \u003e= `date` \u003c `2022-04-08` | 34s | ~35m\n*none* | `2022-03-22` \u003e `date` \u003c `2022-03-24` | 2.4s | ~1523k\n\nUsing `dates_filter_min(t_min)` \u0026 `dates_filter_max(t_max)`, we can easily filter by `date`. When filtering by `t_departure` (absolute departure date+time), `t_min` is the lower `t_departure` bound, whereas `t_max` is the upper bound. The VBB example above can be queried like this:\n\n```sql\nSELECT *\nFROM arrivals_departures\n-- filter by absolute departure date+time\nWHERE t_departure \u003e= '2022-03-23T12:30+01' AND t_departure \u003c= '2022-03-23T12:35+01'\n-- allow \"cutoffs\" by filtering by date\nAND \"date\" \u003e= dates_filter_min('2022-03-23T12:30+01') -- evaluates to 2023-03-22\nAND \"date\" \u003c= dates_filter_max('2022-03-23T12:35+01') -- evaluates to 2023-03-23\n```\n\n\n## Performance\n\nWith all use cases I could think of, `gtfs-via-postgres` is reasonably fast. If there's a particular kind of query that you think should be faster, please [open an Issue](https://github.com/public-transport/gtfs-via-postgres/issues/new)!\n\nThe following benchmarks were run with the [2022-07-01 VBB GTFS dataset](https://vbb-gtfs.jannisr.de/2022-07-01/) (41k `stops`, 6m `stop_times`, 207m arrivals/departures) using `gtfs-via-postgres@4.11.9` and PostgreSQL 18.1 on an [M2 MacBook Air](https://support.apple.com/en-us/111867) running macOS 14.8; All measurements are in milliseconds.\n\n| query | avg | min | p25 | p50 | p75 | p95 | p99 | max | iterations |\n| - | - | - | - | - | - | - | - | - | - |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM stops\u003cbr\u003eORDER BY ST_Distance(stop_loc::geometry, ST_SetSRID(ST_MakePoint(9.7, 50.547), 4326)) ASC\u003cbr\u003eLIMIT 100\u003c/pre\u003e | 15 | 14.506 | 15 | 15 | 15 | 15 | 15 | 14.658 | 170 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM arrivals_departures\u003cbr\u003eWHERE route_short_name = 'S1'\u003cbr\u003eAND t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02')\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02')\u003c/pre\u003e | 22 | 21.77 | 22 | 22 | 22 | 23 | 24 | 24.999 | 90 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM arrivals_departures\u003cbr\u003eWHERE station_id = 'de:11000:900100001' -- S+U Friedrichstr. (Berlin)\u003cbr\u003eAND t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02')\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02')\u003c/pre\u003e | 19 | 18.939 | 19 | 19 | 19 | 20 | 21 | 21.706 | 170 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM arrivals_departures\u003cbr\u003eWHERE station_id = 'de:11000:900100001' -- S+U Friedrichstr. (Berlin)\u003cbr\u003eAND t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02')\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02')\u003cbr\u003eAND stop_sequence = 0\u003c/pre\u003e | 5 | 5.375 | 5 | 5 | 5 | 6 | 6 | 5.684 | 500 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM arrivals_departures\u003cbr\u003eWHERE stop_id = 'de:11000:900100001::4' -- S+U Friedrichstr. (Berlin)\u003cbr\u003eAND t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02')\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02')\u003c/pre\u003e | 8 | 8.163 | 8 | 8 | 8 | 8 | 8 | 9.107 | 400 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM arrivals_departures\u003cbr\u003eWHERE trip_id = '168977951'\u003cbr\u003eAND date \u003e '2022-08-08' AND date \u003c= '2022-08-09'\u003c/pre\u003e | 2 | 2.085 | 2 | 2 | 2 | 2 | 2 | 2.277 | 500 |\n| \u003cpre\u003eSELECT count(*)\u003cbr\u003eFROM arrivals_departures\u003cbr\u003eWHERE stop_id = 'de:11000:900100001::4' -- S+U Friedrichstr. (Berlin)\u003c/pre\u003e | 67 | 66.065 | 67 | 67 | 67 | 67 | 69 | 68.927 | 50 |\n| \u003cpre\u003eSELECT count(*)\u003cbr\u003eFROM arrivals_departures\u003cbr\u003eWHERE stop_id = 'definitely-non-existent'\u003c/pre\u003e | 5 | 5.379 | 5 | 5 | 6 | 6 | 6 | 6.351 | 500 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM arrivals_departures\u003cbr\u003eWHERE t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02'::timestamp with time zone)\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02'::timestamp with time zone)\u003c/pre\u003e | 2734 | 2698.415 | 2704 | 2711 | 2738 | 2804 | 2817 | 2819.877 | 5 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM arrivals_departures\u003cbr\u003eWHERE t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= '2022-08-08'\u003cbr\u003eAND date \u003c= '2022-08-09'\u003c/pre\u003e | 2153 | 1781.159 | 1886 | 2156 | 2183 | 2642 | 2734 | 2756.867 | 5 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM connections\u003cbr\u003eWHERE route_short_name = 'S1'\u003cbr\u003eAND t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02')\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02')\u003c/pre\u003e | 84 | 82.654 | 83 | 84 | 84 | 84 | 84 | 84.33 | 20 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM connections\u003cbr\u003eWHERE from_station_id = 'de:11000:900100001' -- S+U Friedrichstr. (Berlin)\u003cbr\u003eAND t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02')\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02')\u003c/pre\u003e | 58 | 57.796 | 58 | 58 | 58 | 58 | 58 | 58.308 | 50 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM connections\u003cbr\u003eWHERE from_station_id = 'de:11000:900100001' -- S+U Friedrichstr. (Berlin)\u003cbr\u003eAND t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02')\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02')\u003cbr\u003eAND from_stop_sequence = 0\u003c/pre\u003e | 10 | 9.851 | 10 | 10 | 10 | 11 | 11 | 12.626 | 300 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM connections\u003cbr\u003eWHERE from_stop_id = 'de:11000:900100001::4' -- S+U Friedrichstr. (Berlin)\u003cbr\u003eAND t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02')\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02')\u003c/pre\u003e | 14 | 13.467 | 14 | 14 | 14 | 14 | 14 | 13.714 | 200 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM connections\u003cbr\u003eWHERE trip_id = '168977951'\u003cbr\u003eAND date \u003e '2022-08-08' AND date \u003c= '2022-08-09'\u003c/pre\u003e | 4 | 3.577 | 4 | 4 | 4 | 4 | 5 | 6.152 | 500 |\n| \u003cpre\u003eSELECT count(*)\u003cbr\u003eFROM connections\u003cbr\u003eWHERE from_stop_id = 'de:11000:900100001::4' -- S+U Friedrichstr. (Berlin)\u003c/pre\u003e | 87 | 86.362 | 87 | 87 | 87 | 87 | 87 | 87.518 | 40 |\n| \u003cpre\u003eSELECT count(*)\u003cbr\u003eFROM connections\u003cbr\u003eWHERE from_stop_id = 'definitely-non-existent'\u003c/pre\u003e | 8 | 7.237 | 8 | 8 | 8 | 8 | 9 | 13.125 | 500 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM connections\u003cbr\u003eWHERE t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= dates_filter_min('2022-08-09T07:10+02'::timestamp with time zone)\u003cbr\u003eAND date \u003c= dates_filter_max('2022-08-09T07:30+02'::timestamp with time zone)\u003cbr\u003eORDER BY t_departure\u003cbr\u003eLIMIT 100\u003c/pre\u003e | 15584 | 15517.829 | 15549 | 15580 | 15617 | 15647 | 15653 | 15654.632 | 3 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM connections\u003cbr\u003eWHERE t_departure \u003e= '2022-08-09T07:10+02' AND t_departure \u003c= '2022-08-09T07:30+02'\u003cbr\u003eAND date \u003e= '2022-08-08'\u003cbr\u003eAND date \u003c= '2022-08-09'\u003cbr\u003eORDER BY t_departure\u003cbr\u003eLIMIT 100\u003c/pre\u003e | 6816 | 6559.442 | 6685 | 6811 | 6945 | 7052 | 7074 | 7079.167 | 3 |\n| \u003cpre\u003eSELECT *\u003cbr\u003eFROM stats_by_route_date\u003cbr\u003eWHERE route_id = '17452_900' -- M4\u003cbr\u003eAND date \u003e= '2022-08-08' AND date \u003c= '2022-08-14'\u003cbr\u003eAND is_effective = true\u003c/pre\u003e | 688 | 677.417 | 678 | 678 | 681 | 715 | 722 | 723.411 | 5 |\n\n\n## Related Projects\n\nThere are some projects that are very similar to `gtfs-via-postgres`:\n\n### Node-GTFS\n\n[Node-GTFS (`gtfs` npm package)](https://github.com/BlinkTagInc/node-gtfs) is widely used. It covers three use cases: importing GTFS into an [SQLite](https://sqlite.org/) DB, exporting GTFS/GeoJSON from it, and generating HTML or charts for humans.\n\nI don't use it though because\n\n- it doesn't handle GTFS Time values correctly ([1](https://github.com/BlinkTagInc/node-gtfs/blob/master/lib/utils.js#L36-L46)/[2](https://github.com/BlinkTagInc/node-gtfs/blob/4d5e5369d5d94052a5004204182a2582ced8f619/lib/import.js#L233), checked on 2022-03-01)\n- it doesn't always work in a streaming/iterative way ([1](https://github.com/BlinkTagInc/node-gtfs/blob/4d5e5369d5d94052a5004204182a2582ced8f619/lib/export.js#L65)/[2](https://github.com/BlinkTagInc/node-gtfs/blob/4d5e5369d5d94052a5004204182a2582ced8f619/lib/geojson-utils.js#L118-L126), checked on 2022-03-01)\n- sometimes does synchronous fs calls ([1](https://github.com/BlinkTagInc/node-gtfs/blob/4d5e5369d5d94052a5004204182a2582ced8f619/lib/import.js#L65)/[2](https://github.com/BlinkTagInc/node-gtfs/blob/4d5e5369d5d94052a5004204182a2582ced8f619/lib/import.js#L298), checked on 2022-03-01)\n\n### gtfs-sequelize\n\n[gtfs-sequelize](https://github.com/evansiroky/gtfs-sequelize) uses [sequelize.js](https://sequelize.org) to import a GTFS feed and query the DB.\n\nI don't use it because\n\n- it doesn't handle GTFS Time values correctly ([1](https://github.com/evansiroky/gtfs-sequelize/blob/ba101fa82e730694c536c43e615ff38fd264a65b/lib/gtfsLoader.js#L616-L617)/[2](https://github.com/evansiroky/gtfs-sequelize/blob/ba101fa82e730694c536c43e615ff38fd264a65b/lib/gtfsLoader.js#L24-L33), cheked on 2022-03-01)\n- it doesn't provide much tooling for analyzing all arrivals/departures (checked on 2022-03-01)\n- some of its operations are quite slow, because they fetch related records of a record via JS instead of using `JOIN`s\n\n### gtfs-sql-importer\n\nThere are several forks of the [original outdated project](https://github.com/cbick/gtfs_SQL_importer); [fitnr's fork](https://github.com/fitnr/gtfs-sql-importer) seems to be the most recent one.\n\nThe project has a slightly different goal than `gtfs-via-postgres`: While `gtfs-sql-importer` is designed to import multiple versions of a GTFS dataset in an idempotent fashion, `gtfs-via-postgres` assumes that *one* (version of a) GTFS dataset is imported into *one* DB exactly once.\n\n`gtfs-via-postgres` aims to provide more tools – e.g. the `arrivals_departures` \u0026 `connections` views – to help with the analysis of a GTFS dataset, whereas `gtfs-sql-importer` just imports the data.\n\n### other related projects\n\n- [gtfsdb](https://github.com/OpenTransitTools/gtfsdb) – Python library for converting GTFS files into a relational database.\n- [pygtfs](https://github.com/jarondl/pygtfs) – A python (2/3) library for GTFS (fork of [gtfs-sql](https://github.com/andrewblim/gtfs-sql))\n- [gtfspy](https://github.com/CxAalto/gtfspy) – Public transport network analysis using Python and SQLite.\n- [GTFS Kit](https://github.com/mrcagney/gtfs_kit) – A Python 3.6+ tool kit for analyzing General Transit Feed Specification (GTFS) data.\n- [GtfsToSql](https://github.com/OpenMobilityData/GtfsToSql) – Parses a GTFS feed into an SQL database (Java)\n- [gtfs-to-sqlite](https://github.com/aytee17/gtfs-to-sqlite) – A tool for generating an SQLite database from a GTFS feed. (Java)\n- [gtfs-lib](https://github.com/conveyal/gtfs-lib) – Java library \u0026 CLI for importing GTFS files into a PostgreSQL database.\n- [gtfs-schema](https://github.com/tyleragreen/gtfs-schema) – PostgreSQL schemas for GTFS feeds. (plain SQL)\n- [markusvalo/HSLtraffic](https://github.com/markusvalo/HSLtraffic) – Scripts to create a PostgreSQL database for HSL GTFS-data. (plain SQL)\n\n\n## License\n\nThis project is dual-licensed: **My ([@derhuerst](https://github.com/derhuerst)) contributions are licensed under the [*Prosperity Public License*](https://prosperitylicense.com), [contributions of other people](https://github.com/public-transport/gtfs-via-postgres/graphs/contributors) are licensed as [Apache 2.0](https://apache.org/licenses/LICENSE-2.0)**.\n\n\u003e This license allows you to use and share this software for noncommercial purposes for free and to try this software for commercial purposes for thirty days.\n\n\u003e Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, doesn’t count as use for a commercial purpose.\n\n[Get in touch with me](https://jannisr.de/) to buy a commercial license or read more about [why I sell private licenses for my projects](https://gist.github.com/derhuerst/0ef31ee82b6300d2cafd03d10dd522f7).\n\n\n## Contributing\n\nIf you have a question or need support using `gtfs-via-postgres`, please double-check your code and setup first. If you think you have found a bug or want to propose a feature, use [the issues page](https://github.com/public-transport/gtfs-via-postgres/issues).\n\nBy contributing, you agree to release your modifications under the [Apache 2.0 license](LICENSE-APACHE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpublic-transport%2Fgtfs-via-postgres","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpublic-transport%2Fgtfs-via-postgres","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpublic-transport%2Fgtfs-via-postgres/lists"}