gzm_ztm.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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. "apiote.xyz/p/szczanieckiej/transformers"
  8. "bufio"
  9. "encoding/csv"
  10. "fmt"
  11. "io"
  12. "net/http"
  13. "os"
  14. "path/filepath"
  15. "regexp"
  16. "time"
  17. "golang.org/x/text/transform"
  18. )
  19. type GzmZtm struct {
  20. client http.Client
  21. }
  22. func (GzmZtm) getTimezone() *time.Location {
  23. l, _ := time.LoadLocation("Europe/Warsaw")
  24. return l
  25. }
  26. func (GzmZtm) ConvertVehicles() ([]Vehicle, error) {
  27. return []Vehicle{}, nil
  28. }
  29. func (z GzmZtm) GetVersions(date time.Time, timezone *time.Location) ([]Version, error) {
  30. url := "https://otwartedane.metropoliagzm.pl/dataset/rozklady-jazdy-i-lokalizacja-przystankow-gtfs"
  31. response, err := z.client.Get(url)
  32. if err != nil {
  33. return []Version{}, fmt.Errorf("GetVersions: cannot GET ‘%s’: %w", url, err)
  34. }
  35. doc, err := io.ReadAll(response.Body)
  36. if err != nil {
  37. return []Version{}, fmt.Errorf("GetVersions: cannot read whole html: %w", err)
  38. }
  39. regex, err := regexp.Compile("https://otwartedane.metropoliagzm.pl/dataset/86b5ce0c-daea-4b40-bc60-af2c80477d21/resource/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/download/schedule_([0-9]{4}).([0-9]{2}).([0-9]{2})_[0-9]{13}_[0-9]{4}.ext_gtfs.zip")
  40. urls := regex.FindAllStringSubmatch(string(doc), -1)
  41. versions := []Version{}
  42. for _, u := range urls {
  43. version, err := MakeVersionTimezone(u[1]+u[2]+u[3]+"_99991231", timezone)
  44. if err != nil {
  45. return nil, err
  46. }
  47. version.Link = u[0]
  48. versions = append(versions, version)
  49. }
  50. return versions, nil
  51. }
  52. func (GzmZtm) String() string {
  53. return "gzm_ztm"
  54. }
  55. func (GzmZtm) RealtimeFeeds() map[RealtimeFeedType]string {
  56. return map[RealtimeFeedType]string{
  57. TRIP_UPDATES: "https://gtfsrt.transportgzm.pl:5443/gtfsrt/gzm/tripUpdates",
  58. VEHICLE_POSITIONS: "https://gtfsrt.transportgzm.pl:5443/gtfsrt/gzm/vehiclePositions",
  59. ALERTS: "https://gtfsrt.transoprtgzm.pl:5443/gtfsrt/gzm/info",
  60. }
  61. }
  62. func (GzmZtm) LuaUpdatesScript(config.Auth) string {
  63. return ""
  64. }
  65. func (GzmZtm) Transformer() transform.Transformer {
  66. return transformers.TransformerPL
  67. }
  68. func (GzmZtm) Name() string {
  69. return "Metropolia GZM ZTM"
  70. }
  71. func (GzmZtm) Flags() FeedFlags {
  72. return FeedFlags{
  73. Headsign: HeadsignTripLastStop,
  74. StopIdFormat: "{{stop_id}}",
  75. StopName: "{{stop_name}} | {{stop_code}}",
  76. LineName: "{{route_long_name}}",
  77. }
  78. }
  79. func (GzmZtm) FeedPrepareZip(path string) error {
  80. // rename stops_ext to stops
  81. err := os.Remove(filepath.Join(path, "stops.txt"))
  82. if err != nil {
  83. return fmt.Errorf("while removing stops: %w", err)
  84. }
  85. err = os.Rename(filepath.Join(path, "stops_ext.txt"), filepath.Join(path, "stops.txt"))
  86. if err != nil {
  87. return fmt.Errorf("while renaming stops: %w", err)
  88. }
  89. // onDemand to pickup_type
  90. stopsFile, err := os.Open(filepath.Join(path, "stops.txt"))
  91. if err != nil {
  92. return fmt.Errorf("while opening stops file: %w", err)
  93. }
  94. defer stopsFile.Close()
  95. r := csv.NewReader(bufio.NewReader(stopsFile))
  96. header, err := r.Read()
  97. if err != nil {
  98. return fmt.Errorf("while reading stops header: %w", err)
  99. }
  100. fields := map[string]int{}
  101. for i, headerField := range header {
  102. fields[headerField] = i
  103. }
  104. stopTypes := map[string]string{}
  105. for {
  106. record, err := r.Read()
  107. if err == io.EOF {
  108. break
  109. }
  110. if err != nil {
  111. return fmt.Errorf("while reading a stops record: %w", err)
  112. }
  113. stopTypeID := record[fields["stop_type_id"]]
  114. stopID := record[fields["stop_id"]]
  115. stopTypes[stopID] = stopTypeID
  116. }
  117. stopTimesFile, err := os.Open(filepath.Join(path, "stop_times.txt"))
  118. if err != nil {
  119. return fmt.Errorf("while opening stop_times file: %w", err)
  120. }
  121. defer stopTimesFile.Close()
  122. stopTimes2File, err := os.OpenFile(filepath.Join(path, "stop_times2.txt"), os.O_RDWR|os.O_CREATE, 0644)
  123. if err != nil {
  124. return fmt.Errorf("while opening stop_times2 file: %w", err)
  125. }
  126. defer stopTimes2File.Close()
  127. r = csv.NewReader(stopTimesFile)
  128. w := csv.NewWriter(stopTimes2File)
  129. header, err = r.Read()
  130. if err != nil {
  131. return fmt.Errorf("while reading stop_times header: %w", err)
  132. }
  133. header = append(header, "pickup_type")
  134. header = append(header, "drop_off_type")
  135. fields = map[string]int{}
  136. for i, headerField := range header {
  137. fields[headerField] = i
  138. }
  139. err = w.Write(header)
  140. if err != nil {
  141. return fmt.Errorf("while writing the stops header: %w", err)
  142. }
  143. for {
  144. record, err := r.Read()
  145. if err == io.EOF {
  146. break
  147. }
  148. if err != nil {
  149. return fmt.Errorf("while reading stop_times record: %w", err)
  150. }
  151. stopID := record[fields["stop_id"]]
  152. switch stopTypes[stopID] {
  153. case "2":
  154. record = append(record, "3")
  155. record = append(record, "3")
  156. case "3":
  157. record = append(record, "0")
  158. record = append(record, "0")
  159. default:
  160. return fmt.Errorf("unknown stop_type_id: %s for stop: %s", stopTypes[stopID], stopID)
  161. }
  162. err = w.Write(record)
  163. if err != nil {
  164. return fmt.Errorf("while writing a stops record: %w", err)
  165. }
  166. }
  167. w.Flush()
  168. err = w.Error()
  169. if err != nil {
  170. return fmt.Errorf("while flushing stops: %w", err)
  171. }
  172. err = os.Remove(filepath.Join(path, "stop_times.txt"))
  173. if err != nil {
  174. return fmt.Errorf("while removing stop_times: %w", err)
  175. }
  176. err = os.Rename(filepath.Join(path, "stop_times2.txt"), filepath.Join(path, "stop_times.txt"))
  177. if err != nil {
  178. return fmt.Errorf("while renaming stop_times: %w", err)
  179. }
  180. // route_type 800 -> 11
  181. routesFile, err := os.Open(filepath.Join(path, "routes.txt"))
  182. if err != nil {
  183. return fmt.Errorf("while opening routes file: %w", err)
  184. }
  185. defer routesFile.Close()
  186. routes2File, err := os.OpenFile(filepath.Join(path, "routes2.txt"), os.O_RDWR|os.O_CREATE, 0644)
  187. if err != nil {
  188. return fmt.Errorf("while opening routes2 file: %w", err)
  189. }
  190. defer routes2File.Close()
  191. r = csv.NewReader(bufio.NewReader(routesFile))
  192. w = csv.NewWriter(routes2File)
  193. header, err = r.Read()
  194. if err != nil {
  195. return fmt.Errorf("while reading routes header: %w", err)
  196. }
  197. fields = map[string]int{}
  198. for i, headerField := range header {
  199. fields[headerField] = i
  200. }
  201. err = w.Write(header)
  202. if err != nil {
  203. return fmt.Errorf("while writing routes header: %w", err)
  204. }
  205. for {
  206. record, err := r.Read()
  207. if err == io.EOF {
  208. break
  209. }
  210. if err != nil {
  211. return fmt.Errorf("while reading a route record: %w", err)
  212. }
  213. if record[fields["route_type"]] == "800" {
  214. record[fields["route_type"]] = "11"
  215. }
  216. err = w.Write(record)
  217. if err != nil {
  218. return fmt.Errorf("while writing a route record: %w", err)
  219. }
  220. }
  221. w.Flush()
  222. err = w.Error()
  223. if err != nil {
  224. return fmt.Errorf("while flushing routes: %w", err)
  225. }
  226. err = os.Remove(filepath.Join(path, "routes.txt"))
  227. if err != nil {
  228. return fmt.Errorf("while removing routes: %w", err)
  229. }
  230. err = os.Rename(filepath.Join(path, "routes2.txt"), filepath.Join(path, "routes.txt"))
  231. if err != nil {
  232. return fmt.Errorf("while renaming routes: %w", err)
  233. }
  234. return nil
  235. }
  236. func (GzmZtm) QRInfo() (string, QRLocation, string) {
  237. return "rj.metropoliaztm.pl", QRLocationPath, "/redir/stop/(?<stop>[^/]+)"
  238. }