calgary_transit.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // SPDX-FileCopyrightText: Adam Evyčędo
  2. //
  3. // SPDX-License-Identifier: AGPL-3.0-or-later
  4. package traffic
  5. import (
  6. "apiote.xyz/p/szczanieckiej/config"
  7. "bufio"
  8. "encoding/csv"
  9. "fmt"
  10. "io"
  11. "net/http"
  12. "os"
  13. "path/filepath"
  14. "strings"
  15. "time"
  16. "golang.org/x/text/transform"
  17. )
  18. type CalgaryTransit struct {
  19. client http.Client
  20. }
  21. func (CalgaryTransit) getTimezone() *time.Location {
  22. l, _ := time.LoadLocation("America/Edmonton")
  23. return l
  24. }
  25. func (g CalgaryTransit) ConvertVehicles() ([]Vehicle, error) {
  26. return []Vehicle{}, nil
  27. }
  28. func (z CalgaryTransit) GetVersions(_ time.Time, timezone *time.Location) ([]Version, error) {
  29. return []Version{{
  30. Link: "https://data.calgary.ca/download/npk7-z3bj/application%2Fx-zip-compressed",
  31. ValidFrom: time.Date(0, time.January, 1, 0, 0, 0, 0, timezone),
  32. ValidTill: time.Date(9999, time.December, 31, 23, 59, 59, 999_999_999, timezone),
  33. }}, nil
  34. }
  35. func (CalgaryTransit) String() string {
  36. return "calgary_transit"
  37. }
  38. func (CalgaryTransit) RealtimeFeeds() map[RealtimeFeedType]string {
  39. return map[RealtimeFeedType]string{
  40. TRIP_UPDATES: "https://data.calgary.ca/download/gs4m-mdc2/application%2Foctet-stream",
  41. VEHICLE_POSITIONS: "https://data.calgary.ca/download/am7c-qe3u/application%2Foctet-stream",
  42. ALERTS: "https://data.calgary.ca/download/jhgn-ynqj/application%2Foctet-stream",
  43. }
  44. }
  45. func (CalgaryTransit) LuaUpdatesScript(config.Auth) string {
  46. return ""
  47. }
  48. func (CalgaryTransit) Transformer() transform.Transformer {
  49. return transform.Nop
  50. }
  51. func (CalgaryTransit) Name() string {
  52. return "Calgary Transit"
  53. }
  54. func (CalgaryTransit) Flags() FeedFlags {
  55. return FeedFlags{
  56. Headsign: HeadsignTripHeadsing,
  57. StopIdFormat: "{{stop_code}}",
  58. StopName: "{{stop_name}} [{{stop_code}}]",
  59. LineName: "{{route_short_name}}",
  60. }
  61. }
  62. /*
  63. TODO(11)
  64. Correct all errors in GTFS file.
  65. Everything that is not correct acording to the GTFS documentation is considered an error.
  66. This function has access to unzipped GTFS file and can operate on files at `path/specific_file.txt`
  67. */
  68. func (CalgaryTransit) FeedPrepareZip(path string) error {
  69. // route_id as in GTFS-RT AND names for CTrain and Max
  70. routesFile, err := os.Open(filepath.Join(path, "routes.txt"))
  71. if err != nil {
  72. return fmt.Errorf("while opening routes file: %w", err)
  73. }
  74. defer routesFile.Close()
  75. routes2File, err := os.OpenFile(filepath.Join(path, "routes2.txt"), os.O_RDWR|os.O_CREATE, 0644)
  76. if err != nil {
  77. return fmt.Errorf("while opening routes2 file: %w", err)
  78. }
  79. defer routes2File.Close()
  80. r := csv.NewReader(bufio.NewReader(routesFile))
  81. w := csv.NewWriter(routes2File)
  82. header, err := r.Read()
  83. if err != nil {
  84. return fmt.Errorf("while reading routes header: %w", err)
  85. }
  86. fields := map[string]int{}
  87. for i, headerField := range header {
  88. fields[headerField] = i
  89. }
  90. err = w.Write(header)
  91. if err != nil {
  92. return fmt.Errorf("while writing routes header: %w", err)
  93. }
  94. for {
  95. record, err := r.Read()
  96. if err == io.EOF {
  97. break
  98. }
  99. if err != nil {
  100. return fmt.Errorf("while reading a route record: %w", err)
  101. }
  102. // record[fields["route_id"]] = strings.ReplaceAll(record[fields["route_id"]], "-20731", "")
  103. // switch record[fields["route_id"]] {
  104. // case "201-20728":
  105. // record[fields["route_short_name"]] = "CTrain Red"
  106. // case "202-20728":
  107. // record[fields["route_short_name"]] = "CTrain Blue"
  108. // }
  109. record[fields["route_id"]] = strings.Split(record[fields["route_id"]], "-")[0]
  110. switch record[fields["route_id"]] {
  111. case "201":
  112. record[fields["route_short_name"]] = "CTrain Red"
  113. case "202":
  114. record[fields["route_short_name"]] = "CTrain Blue"
  115. case "303":
  116. record[fields["route_short_name"]] = "MAX Orange"
  117. case "304":
  118. record[fields["route_short_name"]] = "MAX Yellow"
  119. case "306":
  120. record[fields["route_short_name"]] = "MAX Teal"
  121. case "307":
  122. record[fields["route_short_name"]] = "MAX Purple"
  123. }
  124. err = w.Write(record)
  125. if err != nil {
  126. return fmt.Errorf("while writing a route record: %w", err)
  127. }
  128. }
  129. w.Flush()
  130. err = w.Error()
  131. if err != nil {
  132. return fmt.Errorf("while flushing routes: %w", err)
  133. }
  134. err = os.Remove(filepath.Join(path, "routes.txt"))
  135. if err != nil {
  136. return fmt.Errorf("while removing routes: %w", err)
  137. }
  138. err = os.Rename(filepath.Join(path, "routes2.txt"), filepath.Join(path, "routes.txt"))
  139. if err != nil {
  140. return fmt.Errorf("while renaming routes: %w", err)
  141. }
  142. tripsFile, err := os.Open(filepath.Join(path, "trips.txt"))
  143. if err != nil {
  144. return fmt.Errorf("while opening trips file: %w", err)
  145. }
  146. defer tripsFile.Close()
  147. trips2File, err := os.OpenFile(filepath.Join(path, "trips2.txt"), os.O_RDWR|os.O_CREATE, 0644)
  148. if err != nil {
  149. return fmt.Errorf("while opening trips2 file: %w", err)
  150. }
  151. defer trips2File.Close()
  152. r = csv.NewReader(bufio.NewReader(tripsFile))
  153. w = csv.NewWriter(trips2File)
  154. header, err = r.Read()
  155. if err != nil {
  156. return fmt.Errorf("while reading trips header: %w", err)
  157. }
  158. fields = map[string]int{}
  159. for i, headerField := range header {
  160. fields[headerField] = i
  161. }
  162. err = w.Write(header)
  163. if err != nil {
  164. return fmt.Errorf("while writing trips header: %w", err)
  165. }
  166. for {
  167. record, err := r.Read()
  168. if err == io.EOF {
  169. break
  170. }
  171. if err != nil {
  172. return fmt.Errorf("while reading a trip record: %w", err)
  173. }
  174. record[fields["route_id"]] = strings.Split(record[fields["route_id"]], "-")[0]
  175. // record[fields["route_id"]] = strings.ReplaceAll(record[fields["route_id"]], "-20731", "")
  176. err = w.Write(record)
  177. if err != nil {
  178. return fmt.Errorf("while writing a trip record: %w", err)
  179. }
  180. }
  181. w.Flush()
  182. err = w.Error()
  183. if err != nil {
  184. return fmt.Errorf("while flushing trips: %w", err)
  185. }
  186. err = os.Remove(filepath.Join(path, "trips.txt"))
  187. if err != nil {
  188. return fmt.Errorf("while removing trips: %w", err)
  189. }
  190. err = os.Rename(filepath.Join(path, "trips2.txt"), filepath.Join(path, "trips.txt"))
  191. if err != nil {
  192. return fmt.Errorf("while renaming trips: %w", err)
  193. }
  194. return nil
  195. }
  196. func (CalgaryTransit) QRInfo() (string, QRLocation, string) {
  197. return "", QRLocationNone, ""
  198. }