diff --git a/.gitignore b/.gitignore index d2839402..1c1f88d3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ api_descriptor.pb tmp.sql .vscode/ data/ToeiBus-GTFS/ +data/SeibuBus-GTFS/ +data/KeioBus-GTFS/ scripts/.osm_cache/ scripts/.gtfs_cache/ __pycache__/ diff --git a/AGENTS.md b/AGENTS.md index f04add7e..d140c289 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ This guide explains how automation agents and human contributors should work wit - Environment variables: - `DATABASE_URL` – SQLx connection string (e.g., `postgres://stationapi:stationapi@localhost/stationapi`). - `DISABLE_GRPC_WEB` – `false` enables gRPC-Web; set to `true` for pure gRPC/HTTP2. - - `ODPT_ACCESS_TOKEN` – ODPT consumer key used to download authenticated GTFS feeds such as Seibu Bus. + - `ODPT_ACCESS_TOKEN` – ODPT consumer key used to download authenticated GTFS feeds such as Seibu Bus and Keio Bus. - `HOST` and `PORT` – listen address (defaults to `[::1]:50051`; Docker uses `0.0.0.0:50051`). - `.env.test` exports `TEST_DATABASE_URL`, `RUST_LOG`, `RUST_BACKTRACE`, and `RUST_TEST_THREADS=1` for integration tests. - Recommended: keep shared defaults in `.env`, copy overrides to `.env.local`, and rely on startup loading both files. @@ -53,8 +53,8 @@ This guide explains how automation agents and human contributors should work wit - **Stations** – `GetStationById`, `GetStationByIdList`, `GetStationsByGroupId`, `GetStationsByCoordinates`, `GetStationsByLineId`, `GetStationsByName`, `GetStationsByLineGroupId`. `QueryInteractor` enriches stations with lines, companies, station numbers, and train types. - **Lines** – `GetLineById`, `GetLinesByIdList`, `GetLinesByName`. Results include company data and computed line symbols based on repository helpers. - **Routes** – `GetRoutes`, `GetRoutesMinimal`. The minimal variant returns `RouteMinimalResponse` with deduplicated `LineMinimal` data; paging tokens are currently empty (pagination not implemented). -- **Train types** – `GetTrainTypesByStationId`, `GetRouteTypes`. Train types aggregate by line group and include related lines plus optional train type metadata. Rail variants use `TrainTypeKind::{Default, Branch, Rapid, Express, LimitedExpress, HighSpeedRapid, CommuterRapid}` (0-6); bus variants use `BusRoute` (7), which represents a `(route_id, shape_id)` operation pattern (e.g. 循環 / 短ターン / 支線) generated automatically from the Toei Bus GTFS feed. -- **GTFS bus integration** – At startup, `src/import.rs::integrate_gtfs_to_stations()` ingests GTFS feeds into `gtfs_*` tables and then projects them onto the shared `stations` / `lines` / `types` / `station_station_types` tables. Every configured feed is imported, including Seibu Bus (downloaded from ODPT with `ODPT_ACCESS_TOKEN`). `transport_type` (0: rail, 1: bus) on both `stations` and `lines` keeps the two worlds queryable side by side. GTFS IDs are namespaced per feed before import to avoid cross-operator collisions. `line_cd` (100,000,000+), `station_cd` / `station_g_cd` (200,000,000+), and bus `type_cd` / `line_group_cd` (100,000,000+) are all deterministic fnv1a hashes that stay clear of the rail data ranges. Disable the entire bus pipeline with `DISABLE_BUS_FEATURE=true`. +- **Train types** – `GetTrainTypesByStationId`, `GetRouteTypes`. Train types aggregate by line group and include related lines plus optional train type metadata. Rail variants use `TrainTypeKind::{Default, Branch, Rapid, Express, LimitedExpress, HighSpeedRapid, CommuterRapid}` (0-6); bus variants use `BusRoute` (7), which represents a `(route_id, shape_id)` operation pattern (e.g. 循環 / 短ターン / 支線) generated automatically from the configured GTFS bus feeds (Toei Bus, Seibu Bus, Keio Bus). +- **GTFS bus integration** – At startup, `src/import.rs::integrate_gtfs_to_stations()` ingests GTFS feeds into `gtfs_*` tables and then projects them onto the shared `stations` / `lines` / `types` / `station_station_types` tables. Every configured feed is imported, including Seibu Bus and Keio Bus (both downloaded from ODPT with `ODPT_ACCESS_TOKEN`). `transport_type` (0: rail, 1: bus) on both `stations` and `lines` keeps the two worlds queryable side by side. GTFS IDs are namespaced per feed before import to avoid cross-operator collisions. `line_cd` (100,000,000+), `station_cd` / `station_g_cd` (200,000,000+), and bus `type_cd` / `line_group_cd` (100,000,000+) are all deterministic fnv1a hashes that stay clear of the rail data ranges. Disable the entire bus pipeline with `DISABLE_BUS_FEATURE=true`. - **TTS metadata** – `Station`, `StationMinimal`, `Line`, and `TrainType` expose `name_ipa` / `name_roman_ipa` plus `name_tts_segments` for multi-segment pronunciation output. Use `name_tts_segments` when clients need per-token SSML construction for mixed-language names such as `Kasai-Rinkai Park`. - **Connected routes** – `GetConnectedRoutes`. `QueryInteractor::get_connected_stations` is not implemented yet and returns an empty vector; update the use-case and infrastructure layers together when adding real logic. - Changes to the service contract require coordinated updates to `proto/stationapi.proto`, regenerated code via `tonic-build`, and corresponding adjustments in both presentation and use-case layers. diff --git a/README.md b/README.md index 31f9bc5c..a1edaae6 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ We follow Rust best practices for testing: - Bus-related data provided by [Tokyo Metropolitan Bureau of Transportation (Toei)](https://www.kotsu.metro.tokyo.jp/), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) - Bus-related data provided by [Seibu Bus Co., Ltd. (西武バス)](https://www.seibubus.co.jp/) via the [Public Transportation Open Data Center](https://ckan.odpt.org/), licensed under the [Public Transportation Open Data Basic License](https://developer.odpt.org/terms) +- Bus-related data provided by [Keio Dentetsu Bus Co., Ltd. (京王電鉄バス)](https://www.keio-bus.com/) via the [Public Transportation Open Data Center](https://ckan.odpt.org/), licensed under the [Public Transportation Open Data Basic License](https://developer.odpt.org/terms) - Station data provided by [駅データ.jp](https://www.ekidata.jp/) - Speed calibration data (`speed_table.rs`) derived from GTFS timetables provided by Kyoto City Transportation Bureau (京都市交通局), Yokohama City Transportation Bureau (横浜市交通局), Tokyo Metro (東京メトロ), Metropolitan Intercity Railway (首都圏新都市鉄道), Tokyo Tama Intercity Monorail (多摩都市モノレール), and Tokyo Waterfront Area Rapid Transit (東京臨海高速鉄道) via the [Public Transportation Open Data Center](https://ckan.odpt.org/), licensed under the [Public Transportation Open Data Basic License](https://developer.odpt.org/terms); by [Tokyo Metropolitan Bureau of Transportation (Toei)](https://www.kotsu.metro.tokyo.jp/), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/); and by Hakodate City Enterprise Bureau Transportation Department (函館市企業局交通部), licensed under [GTFS-RUL (ODPT)](https://gtfs-jp.org/GTFS-RUL(ODPT).pdf) - Average inter-station distances (`average_distance`) computed from railway track geometry © [OpenStreetMap contributors](https://www.openstreetmap.org/copyright), licensed under [ODbL](https://opendatacommons.org/licenses/odbl/) diff --git a/data/1!companies.csv b/data/1!companies.csv index 7879e36a..f9c931f8 100644 --- a/data/1!companies.csv +++ b/data/1!companies.csv @@ -8,6 +8,7 @@ company_cd,rr_cd,company_name,company_name_k,company_name_h,company_name_r,compa 11,21,東武鉄道,トウブテツドウ,東武鉄道株式会社,東武,Tobu,Tobu Railway,http://www.tobu.co.jp/,2,0,11 12,22,西武鉄道,セイブテツドウ,西武鉄道株式会社,西武,Seibu,Seibu Railway,http://www.seibu-group.co.jp/railways/,2,0,12 253,99,西武バス,セイブバス,西武バス株式会社,西武バス,Seibu Bus,Seibu Bus,https://www.seibubus.co.jp/,0,0,12 +254,99,京王バス,ケイオウバス,京王電鉄バス株式会社,京王バス,Keio Bus,Keio Bus,https://www.keio-bus.com/,0,0,14 13,23,京成電鉄,ケイセイデンテツ,京成電鉄株式会社,京成,Keisei,Keisei Electric Railway,http://www.keisei.co.jp/,2,0,13 14,24,京王電鉄,ケイオウデンテツ,京王電鉄株式会社,京王,Keio,Keio,http://www.keio.co.jp/,2,0,14 15,25,小田急電鉄,オダキュウデンテツ,小田急電鉄株式会社,小田急,Odakyu,Odakyu Electric Railway,http://www.odakyu.jp/,2,0,15 diff --git a/docs/architecture.md b/docs/architecture.md index 8ffdf295..d3200cc3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -166,11 +166,11 @@ CREATE INDEX idx_performance_station_name_trgm ON stations ### バス (GTFS) データの統合 -`stations` / `lines` / `types` / `station_station_types` は鉄道とバスの両方を保持し、`stations.transport_type` / `lines.transport_type` (0: 鉄道, 1: バス) でフィルタリングします。バスデータの取り込みは `src/import.rs` の `integrate_gtfs_to_stations()` が起動時に実行し、ODPT 公開の GTFS を `gtfs_*` テーブルに展開してから既存テーブルへ統合します。Seibu Bus を含む設定済みの全GTFSフィードを使用します。Seibu Bus のダウンロードには `.env.local` の `ODPT_ACCESS_TOKEN` を使用します。 +`stations` / `lines` / `types` / `station_station_types` は鉄道とバスの両方を保持し、`stations.transport_type` / `lines.transport_type` (0: 鉄道, 1: バス) でフィルタリングします。バスデータの取り込みは `src/import.rs` の `integrate_gtfs_to_stations()` が起動時に実行し、ODPT 公開の GTFS を `gtfs_*` テーブルに展開してから既存テーブルへ統合します。Seibu Bus・Keio Bus を含む設定済みの全GTFSフィードを使用します。Seibu Bus・Keio Bus のダウンロードには `.env.local` の `ODPT_ACCESS_TOKEN` を使用します。 | 鉄道側の概念 | バス側の対応 | |---|---| -| `companies` | GTFS フィードごとに会社を決定。Toei Bus は東京都交通局 (`company_cd=119`)、Seibu Bus は西武バス (`company_cd=253`) | +| `companies` | GTFS フィードごとに会社を決定。Toei Bus は東京都交通局 (`company_cd=119`)、Seibu Bus は西武バス (`company_cd=253`)、Keio Bus は京王バス (`company_cd=254`) | | `lines` | GTFS `routes` を 1:1 で `lines` に登録。`line_cd` はフィード接頭辞付き `route_id` の fnv1a ハッシュで 100,000,000+ 空間に決定的に生成 | | `stations` | GTFS `stops` (親停留所) を `(stop_id, route_id)` 単位で `stations` に登録。`station_cd` / `station_g_cd` もフィード接頭辞付き ID から 200,000,000+ 空間にハッシュ生成 | | `types` | GTFS の `(route_id, shape_id)` バリエーション (フルループ / 短ターン / 支線など) を `kind = TrainTypeKind::BusRoute (= 7)` の TrainType として登録。**停留所集合が完全に同じ shape ペア (上下方向違いのみ) は 1 つの TrainType に畳み、`direction = Both` を設定**。`type_name` は循環なら ` (循環)`、双方向ペアなら ``、片方向なら `<始発停留所> → ` | diff --git a/stationapi/src/import.rs b/stationapi/src/import.rs index 5cb5f0dd..92a9d5db 100644 --- a/stationapi/src/import.rs +++ b/stationapi/src/import.rs @@ -286,6 +286,13 @@ const GTFS_FEEDS: &[GtfsFeed] = &[ url: "https://api.odpt.org/api/v4/files/SeibuBus/data/SeibuBus-GTFS.zip", requires_consumer_key: true, }, + GtfsFeed { + id: "keio", + name: "Keio Bus", + path: "data/KeioBus-GTFS", + url: "https://api.odpt.org/api/v4/files/odpt/KeioBus/AllLines.zip", + requires_consumer_key: true, + }, ]; const DEFAULT_GTFS_BUS_LINE_COLOR: &str = "#1f63c6"; @@ -1675,6 +1682,8 @@ fn company_cd_for_gtfs_route(route_id: &str) -> Option { Some(119) // Toei Transportation } else if route_id.starts_with("seibu:") { Some(253) // Seibu Bus + } else if route_id.starts_with("keio:") { + Some(254) // Keio Bus } else { None // Unknown/unsupported prefix } @@ -3040,6 +3049,7 @@ mod tests { fn test_company_cd_for_gtfs_route() { assert_eq!(company_cd_for_gtfs_route("toei:route_001"), Some(119)); assert_eq!(company_cd_for_gtfs_route("seibu:route_001"), Some(253)); + assert_eq!(company_cd_for_gtfs_route("keio:route_001"), Some(254)); assert_eq!(company_cd_for_gtfs_route("unknown:route_001"), None); } @@ -3128,8 +3138,17 @@ mod tests { fn test_gtfs_feeds() { assert_eq!( GTFS_FEEDS.iter().map(|feed| feed.id).collect::>(), - vec!["toei", "seibu"] + vec!["toei", "seibu", "keio"] + ); + + let keio = GTFS_FEEDS.iter().find(|feed| feed.id == "keio").unwrap(); + assert_eq!(keio.name, "Keio Bus"); + assert_eq!(keio.path, "data/KeioBus-GTFS"); + assert_eq!( + keio.url, + "https://api.odpt.org/api/v4/files/odpt/KeioBus/AllLines.zip" ); + assert!(keio.requires_consumer_key); } #[test]