api.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  1. // SPDX-FileCopyrightText: Adam Evyčędo
  2. //
  3. // SPDX-License-Identifier: AGPL-3.0-or-later
  4. package api
  5. // TODO(BAF10) direction (0|1) to const (TO|BACK)
  6. import (
  7. "apiote.xyz/p/szczanieckiej/traffic"
  8. "golang.org/x/text/language"
  9. "encoding/hex"
  10. "errors"
  11. "fmt"
  12. "log"
  13. "sort"
  14. "time"
  15. "git.sr.ht/~sircmpwn/go-bare"
  16. md "github.com/JohannesKaufmann/html-to-markdown"
  17. "github.com/adrg/strutil"
  18. "github.com/adrg/strutil/metrics"
  19. "github.com/dhconnelly/rtreego"
  20. )
  21. func convertTrafficAlerts(trafficAlerts []traffic.SpecificAlert) []AlertV1 {
  22. alerts := []AlertV1{}
  23. converter := md.NewConverter("", true, nil)
  24. for _, alert := range trafficAlerts {
  25. description, err := converter.ConvertString(alert.Description)
  26. if err != nil {
  27. log.Println("cannot convert html to markdown")
  28. description = alert.Description
  29. }
  30. a := AlertV1{
  31. Header: alert.Header,
  32. Description: description,
  33. Url: alert.URL,
  34. Cause: makeAlertCauseV1(alert),
  35. Effect: makeAlertEffectV1(alert),
  36. }
  37. alerts = append(alerts, a)
  38. }
  39. return alerts
  40. }
  41. func convertTrafficStop(stop traffic.Stop) StopV1 {
  42. s := StopV1{
  43. Code: stop.Code,
  44. Name: stop.NodeName,
  45. Zone: stop.Zone,
  46. Position: PositionV1{Lat: stop.Position.Lat, Lon: stop.Position.Lon},
  47. ChangeOptions: []ChangeOptionV1{},
  48. }
  49. for _, option := range stop.ChangeOptions {
  50. s.ChangeOptions = append(s.ChangeOptions, makeChangeOptionV1(option))
  51. }
  52. return s
  53. }
  54. func convertTrafficStopV2(stop traffic.Stop, feedID string) StopV2 {
  55. s := StopV2{
  56. Code: stop.Code,
  57. Name: stop.Name,
  58. NodeName: stop.NodeName,
  59. Zone: stop.Zone,
  60. FeedID: feedID,
  61. Position: PositionV1{Lat: stop.Position.Lat, Lon: stop.Position.Lon},
  62. ChangeOptions: []ChangeOptionV1{},
  63. }
  64. for _, option := range stop.ChangeOptions {
  65. s.ChangeOptions = append(s.ChangeOptions, makeChangeOptionV1(option))
  66. }
  67. return s
  68. }
  69. func convertTrafficStopStub(stopStub traffic.StopStub) StopStubV1 {
  70. s := StopStubV1{
  71. Code: stopStub.Code,
  72. Name: stopStub.Name,
  73. NodeName: stopStub.NodeName,
  74. Zone: stopStub.Zone,
  75. OnDemand: stopStub.OnDemand,
  76. }
  77. return s
  78. }
  79. func convertTrafficVehicle(vehicle traffic.VehicleStatus, context traffic.Context, t *traffic.Traffic) (VehicleV1, error) {
  80. var (
  81. line traffic.Line
  82. err error
  83. )
  84. if vehicle.LineID == "" {
  85. line, err = traffic.GetLineOld(vehicle.LineName, context, t)
  86. if err != nil {
  87. return VehicleV1{}, fmt.Errorf("while getting line %s: %w", vehicle.LineName, err)
  88. }
  89. } else {
  90. line, err = traffic.GetLine(vehicle.LineID, context, t)
  91. if err != nil {
  92. return VehicleV1{}, fmt.Errorf("while getting line %s: %w", vehicle.LineID, err)
  93. }
  94. }
  95. return VehicleV1{
  96. Id: string(vehicle.VehicleID),
  97. Position: PositionV1{vehicle.Latitude, vehicle.Longitude},
  98. Capabilities: t.Vehicles[context.FeedID][context.Version][vehicle.VehicleID].Capabilities,
  99. Speed: vehicle.Speed,
  100. Line: LineStubV1{Name: line.Name, Kind: makeLineTypeV1(line), Colour: fromColor(line.Colour)},
  101. Headsign: vehicle.Headsign,
  102. CongestionLevel: convertCongestionLevelV1(vehicle.CongestionLevel),
  103. OccupancyStatus: convertOccupancyStatusV1(vehicle.OccupancyStatus),
  104. }, nil
  105. }
  106. func convertTrafficVehicleV2(vehicle traffic.VehicleStatus, context traffic.Context, t *traffic.Traffic) (VehicleV2, error) {
  107. var (
  108. line traffic.Line
  109. err error
  110. )
  111. if vehicle.LineID == "" {
  112. line, err = traffic.GetLineOld(vehicle.LineName, context, t)
  113. if err != nil {
  114. return VehicleV2{}, fmt.Errorf("while getting line %s: %w", vehicle.LineName, err)
  115. }
  116. } else {
  117. line, err = traffic.GetLine(vehicle.LineID, context, t)
  118. if err != nil {
  119. return VehicleV2{}, fmt.Errorf("while getting line %s: %w", vehicle.LineID, err)
  120. }
  121. }
  122. return VehicleV2{
  123. Id: string(vehicle.VehicleID),
  124. Position: PositionV1{vehicle.Latitude, vehicle.Longitude},
  125. Capabilities: t.Vehicles[context.FeedID][context.Version][vehicle.VehicleID].Capabilities,
  126. Speed: vehicle.Speed,
  127. Line: LineStubV2{Name: line.Name, Kind: makeLineTypeV2(line), Colour: fromColor(line.Colour)},
  128. Headsign: vehicle.Headsign,
  129. CongestionLevel: convertCongestionLevelV1(vehicle.CongestionLevel),
  130. OccupancyStatus: convertOccupancyStatusV1(vehicle.OccupancyStatus),
  131. }, nil
  132. }
  133. func convertTrafficVehicleV3(vehicle traffic.VehicleStatus, context traffic.Context, t *traffic.Traffic) (VehicleV3, error) {
  134. var (
  135. line traffic.Line
  136. err error
  137. )
  138. if vehicle.LineID == "" {
  139. line, err = traffic.GetLineOld(vehicle.LineName, context, t)
  140. if err != nil {
  141. return VehicleV3{}, fmt.Errorf("while getting line %s: %w", vehicle.LineName, err)
  142. }
  143. } else {
  144. line, err = traffic.GetLine(vehicle.LineID, context, t)
  145. if err != nil {
  146. return VehicleV3{}, fmt.Errorf("while getting line %s: %w", vehicle.LineID, err)
  147. }
  148. }
  149. return VehicleV3{
  150. Id: string(vehicle.VehicleID),
  151. Position: PositionV1{vehicle.Latitude, vehicle.Longitude},
  152. Capabilities: t.Vehicles[context.FeedID][context.Version][vehicle.VehicleID].Capabilities,
  153. Speed: vehicle.Speed,
  154. Line: LineStubV3{Name: line.Name, Kind: makeLineTypeV3(line), Colour: fromColor(line.Colour)},
  155. Headsign: vehicle.Headsign,
  156. CongestionLevel: convertCongestionLevelV1(vehicle.CongestionLevel),
  157. OccupancyStatus: convertOccupancyStatusV1(vehicle.OccupancyStatus),
  158. }, nil
  159. }
  160. func getDayOffset(t1, t2 time.Time) int8 {
  161. midnightBefore := time.Date(t1.Year(), t1.Month(), t1.Day(), 0, 0, 0, 0, t1.Location())
  162. tomorrow := t1.AddDate(0, 0, 1)
  163. midnightAfter := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 0, 0, 0, 0, tomorrow.Location())
  164. if t2.Before(midnightBefore) {
  165. return -1
  166. } else if t2.Before(midnightAfter) {
  167. return 0
  168. } else {
  169. return 1
  170. }
  171. }
  172. func marshalStopOrder(tripOffset uint, stopOrder int) (string, error) {
  173. order := traffic.StopOrder{
  174. TripOffset: tripOffset,
  175. Sequence: stopOrder,
  176. }
  177. bytes, err := bare.Marshal(&order)
  178. return hex.EncodeToString(bytes), err
  179. }
  180. func CreateSuccessTrip(trafficTrip []traffic.TimedStopStub) /*TripResponse*/ {
  181. /*trip := []TimedStopStub{}
  182. for _, s := range trafficTrip {
  183. trip = append(trip, TimedStopStub{
  184. StopStub: StopStub{
  185. Code: s.Code,
  186. Name: s.Name,
  187. Zone: s.Zone,
  188. OnDemand: s.OnDemand,
  189. },
  190. Time: s.Time,
  191. })
  192. }
  193. return SuccessTrip{
  194. Trip: trip,
  195. }*/
  196. }
  197. func CreateSuccessVehicle(vehicles []traffic.Vehicle, context traffic.Context, t *traffic.Traffic) /*(VehicleResponse, error)*/ {
  198. /*v := make([]Vehicle, len(vehicles))
  199. var err error = nil
  200. for i, vehicle := range vehicles {
  201. v[i], err = convertTrafficVehicle(vehicle, context, t)
  202. if err != nil {
  203. return SuccessVehicle{}, fmt.Errorf("while converting Traffic Vehicle %s: %w", vehicle.Id, err)
  204. }
  205. }
  206. return SuccessVehicle{
  207. Vehicles: v,
  208. }, nil*/
  209. }
  210. func CreateSuccessQueryables(items []traffic.Queryable, context traffic.Context, t *traffic.Traffic, other QueryablesResponse, sortByDistance bool) (QueryablesResponse, error) {
  211. success := QueryablesResponseV1{
  212. Queryables: []QueryableV1{},
  213. }
  214. for _, item := range items {
  215. if stop, ok := item.(traffic.Stop); ok {
  216. s := convertTrafficStop(stop)
  217. success.Queryables = append(success.Queryables, QueryableV1(s))
  218. } else {
  219. continue
  220. }
  221. }
  222. if otherV1, ok := other.(QueryablesResponseV1); ok {
  223. success.Queryables = append(success.Queryables, otherV1.Queryables...)
  224. } else {
  225. return success, errors.New("wrong version of other")
  226. }
  227. return success, nil
  228. }
  229. func CreateSuccessQueryablesV2(query string, items []traffic.Queryable, context traffic.Context, t *traffic.Traffic, other QueryablesResponse, sortByDistance bool) (QueryablesResponse, error) {
  230. success := QueryablesResponseV2{
  231. Queryables: []QueryableV2{},
  232. }
  233. for _, item := range items {
  234. if stop, ok := item.(traffic.Stop); ok {
  235. s := convertTrafficStopV2(stop, context.FeedID)
  236. success.Queryables = append(success.Queryables, QueryableV2(s))
  237. } else if line, ok := item.(traffic.Line); ok {
  238. l := convertTrafficLine(line, context.FeedID)
  239. success.Queryables = append(success.Queryables, QueryableV2(l))
  240. } else {
  241. continue
  242. }
  243. }
  244. if otherV2, ok := other.(QueryablesResponseV2); ok {
  245. success.Queryables = append(success.Queryables, otherV2.Queryables...)
  246. } else {
  247. return success, errors.New("wrong version of other")
  248. }
  249. if sortByDistance {
  250. success.Queryables = sortQueryablesByDistanceV2(query, success.Queryables)
  251. } else {
  252. success.Queryables = sortQueryables(query, success.Queryables)
  253. }
  254. return success, nil
  255. }
  256. func CreateSuccessQueryablesV3(query string, items []traffic.Queryable, context traffic.Context, t *traffic.Traffic, other QueryablesResponse, sortByDistance bool) (QueryablesResponse, error) {
  257. success := QueryablesResponseV3{
  258. Queryables: []QueryableV3{},
  259. }
  260. for _, item := range items {
  261. if stop, ok := item.(traffic.Stop); ok {
  262. s := convertTrafficStopV2(stop, context.FeedID)
  263. success.Queryables = append(success.Queryables, QueryableV3(s))
  264. } else if line, ok := item.(traffic.Line); ok {
  265. l := convertTrafficLineV2(line, context.FeedID)
  266. success.Queryables = append(success.Queryables, QueryableV3(l))
  267. } else {
  268. continue
  269. }
  270. }
  271. if otherV3, ok := other.(QueryablesResponseV3); ok {
  272. success.Queryables = append(success.Queryables, otherV3.Queryables...)
  273. } else {
  274. return success, errors.New("wrong version of other")
  275. }
  276. if sortByDistance {
  277. success.Queryables = sortQueryablesByDistanceV3(query, success.Queryables)
  278. } else {
  279. success.Queryables = sortQueryablesV3(query, success.Queryables)
  280. }
  281. return success, nil
  282. }
  283. func CreateSuccessQueryablesV4(query string, items []traffic.Queryable, context traffic.Context, t *traffic.Traffic, other QueryablesResponse, sortByDistance bool) (QueryablesResponse, error) {
  284. success := QueryablesResponseV4{
  285. Queryables: []QueryableV4{},
  286. }
  287. for _, item := range items {
  288. if stop, ok := item.(traffic.Stop); ok {
  289. s := convertTrafficStopV2(stop, context.FeedID)
  290. success.Queryables = append(success.Queryables, QueryableV4(s))
  291. } else if line, ok := item.(traffic.Line); ok {
  292. if len(line.Headsigns) > 0 {
  293. l := convertTrafficLineV3(line, context.FeedID)
  294. success.Queryables = append(success.Queryables, QueryableV4(l))
  295. }
  296. } else {
  297. continue
  298. }
  299. }
  300. if otherV4, ok := other.(QueryablesResponseV4); ok {
  301. success.Queryables = append(success.Queryables, otherV4.Queryables...)
  302. } else {
  303. return success, errors.New("wrong version of other")
  304. }
  305. if sortByDistance {
  306. success.Queryables = sortQueryablesByDistanceV4(query, success.Queryables)
  307. } else {
  308. success.Queryables = sortQueryablesV4(query, success.Queryables)
  309. }
  310. return success, nil
  311. }
  312. func LimitQueryables(r QueryablesResponse, offset, limit uint64) QueryablesResponse {
  313. var result QueryablesResponse
  314. switch r.(type) {
  315. case QueryablesResponseV1:
  316. var queryables []QueryableV1
  317. if int(offset) > len(r.(QueryablesResponseV1).Queryables) {
  318. queryables = []QueryableV1{}
  319. } else if len(r.(QueryablesResponseV1).Queryables) < int(offset+limit) {
  320. queryables = r.(QueryablesResponseV1).Queryables[offset:]
  321. } else {
  322. queryables = r.(QueryablesResponseV1).Queryables[offset : offset+limit]
  323. }
  324. result = QueryablesResponseV1{queryables}
  325. case QueryablesResponseV2:
  326. var queryables []QueryableV2
  327. if int(offset) > len(r.(QueryablesResponseV2).Queryables) {
  328. queryables = []QueryableV2{}
  329. } else if len(r.(QueryablesResponseV2).Queryables) < int(offset+limit) {
  330. queryables = r.(QueryablesResponseV2).Queryables[offset:]
  331. } else {
  332. queryables = r.(QueryablesResponseV2).Queryables[offset : offset+limit]
  333. }
  334. result = QueryablesResponseV2{queryables}
  335. case QueryablesResponseV3:
  336. var queryables []QueryableV3
  337. if int(offset) > len(r.(QueryablesResponseV3).Queryables) {
  338. queryables = []QueryableV3{}
  339. } else if len(r.(QueryablesResponseV3).Queryables) < int(offset+limit) {
  340. queryables = r.(QueryablesResponseV3).Queryables[offset:]
  341. } else {
  342. queryables = r.(QueryablesResponseV3).Queryables[offset : offset+limit]
  343. }
  344. result = QueryablesResponseV3{queryables}
  345. case QueryablesResponseV4:
  346. var queryables []QueryableV4
  347. if int(offset) > len(r.(QueryablesResponseV4).Queryables) {
  348. queryables = []QueryableV4{}
  349. } else if len(r.(QueryablesResponseV4).Queryables) < int(offset+limit) {
  350. queryables = r.(QueryablesResponseV4).Queryables[offset:]
  351. } else {
  352. queryables = r.(QueryablesResponseV4).Queryables[offset : offset+limit]
  353. }
  354. result = QueryablesResponseV4{queryables}
  355. }
  356. return result
  357. }
  358. func sortQueryables(query string, queryables []QueryableV2) []QueryableV2 {
  359. // fixme query and names should be cleaned
  360. sort.Slice(queryables, func(i, j int) bool {
  361. var nameI, nameJ string
  362. switch queryables[i].(type) {
  363. case StopV2:
  364. nameI = queryables[i].(StopV2).Name
  365. case LineV1:
  366. nameI = queryables[i].(LineV1).Name
  367. }
  368. switch queryables[j].(type) {
  369. case StopV2:
  370. nameJ = queryables[j].(StopV2).Name
  371. case LineV1:
  372. nameJ = queryables[j].(LineV1).Name
  373. }
  374. levenshtein := &metrics.Levenshtein{
  375. CaseSensitive: false,
  376. InsertCost: 1,
  377. DeleteCost: 1,
  378. ReplaceCost: 1,
  379. }
  380. distance1 := strutil.Similarity(query, nameI, levenshtein)
  381. distance2 := strutil.Similarity(query, nameJ, levenshtein)
  382. return distance1 > distance2
  383. })
  384. return queryables
  385. }
  386. func sortQueryablesByDistanceV2(query string, queryables []QueryableV2) []QueryableV2 {
  387. queryPosition, err := traffic.ParsePosition(query)
  388. if err != nil {
  389. log.Printf("while parsing position %s: %v\n", query, err)
  390. return queryables
  391. }
  392. stops := []StopV2{}
  393. for _, q := range queryables {
  394. if s, ok := q.(StopV2); ok {
  395. stops = append(stops, s)
  396. }
  397. }
  398. tree := rtreego.NewTree(2, 1, 50)
  399. for _, stop := range stops {
  400. tree.Insert(stop)
  401. }
  402. spatials := tree.NearestNeighbors(12, rtreego.Point{queryPosition.Lat, queryPosition.Lon})
  403. queryables = make([]QueryableV2, len(spatials))
  404. for i, spatial := range spatials {
  405. queryables[i] = spatial.(StopV2)
  406. }
  407. return queryables
  408. }
  409. func sortQueryablesV3(query string, queryables []QueryableV3) []QueryableV3 {
  410. // fixme query and names should be cleaned
  411. sort.Slice(queryables, func(i, j int) bool {
  412. var nameI, nameJ string
  413. switch queryables[i].(type) {
  414. case StopV2:
  415. nameI = queryables[i].(StopV2).Name
  416. case LineV2:
  417. nameI = queryables[i].(LineV2).Name
  418. }
  419. switch queryables[j].(type) {
  420. case StopV2:
  421. nameJ = queryables[j].(StopV2).Name
  422. case LineV2:
  423. nameJ = queryables[j].(LineV2).Name
  424. }
  425. levenshtein := &metrics.Levenshtein{
  426. CaseSensitive: false,
  427. InsertCost: 1,
  428. DeleteCost: 1,
  429. ReplaceCost: 1,
  430. }
  431. distance1 := strutil.Similarity(query, nameI, levenshtein)
  432. distance2 := strutil.Similarity(query, nameJ, levenshtein)
  433. return distance1 > distance2
  434. })
  435. return queryables
  436. }
  437. func sortQueryablesByDistanceV3(query string, queryables []QueryableV3) []QueryableV3 {
  438. queryPosition, err := traffic.ParsePosition(query)
  439. if err != nil {
  440. log.Printf("while parsing position %s: %v\n", query, err)
  441. return queryables
  442. }
  443. stops := []StopV2{}
  444. for _, q := range queryables {
  445. if s, ok := q.(StopV2); ok {
  446. stops = append(stops, s)
  447. }
  448. }
  449. tree := rtreego.NewTree(2, 1, 50)
  450. for _, stop := range stops {
  451. tree.Insert(stop)
  452. }
  453. spatials := tree.NearestNeighbors(12, rtreego.Point{queryPosition.Lat, queryPosition.Lon})
  454. queryables = make([]QueryableV3, len(spatials))
  455. for i, spatial := range spatials {
  456. queryables[i] = spatial.(StopV2)
  457. }
  458. return queryables
  459. }
  460. func sortQueryablesV4(query string, queryables []QueryableV4) []QueryableV4 {
  461. // fixme query and names should be cleaned
  462. sort.Slice(queryables, func(i, j int) bool {
  463. var nameI, nameJ string
  464. switch queryables[i].(type) {
  465. case StopV2:
  466. nameI = queryables[i].(StopV2).Name
  467. case LineV3:
  468. nameI = queryables[i].(LineV3).Name
  469. }
  470. switch queryables[j].(type) {
  471. case StopV2:
  472. nameJ = queryables[j].(StopV2).Name
  473. case LineV3:
  474. nameJ = queryables[j].(LineV3).Name
  475. }
  476. levenshtein := &metrics.Levenshtein{
  477. CaseSensitive: false,
  478. InsertCost: 1,
  479. DeleteCost: 1,
  480. ReplaceCost: 1,
  481. }
  482. distance1 := strutil.Similarity(query, nameI, levenshtein)
  483. distance2 := strutil.Similarity(query, nameJ, levenshtein)
  484. return distance1 > distance2
  485. })
  486. return queryables
  487. }
  488. func sortQueryablesByDistanceV4(query string, queryables []QueryableV4) []QueryableV4 {
  489. queryPosition, err := traffic.ParsePosition(query)
  490. if err != nil {
  491. log.Printf("while parsing position %s: %v\n", query, err)
  492. return queryables
  493. }
  494. stops := []StopV2{}
  495. for _, q := range queryables {
  496. if s, ok := q.(StopV2); ok {
  497. stops = append(stops, s)
  498. }
  499. }
  500. tree := rtreego.NewTree(2, 1, 50)
  501. for _, stop := range stops {
  502. tree.Insert(stop)
  503. }
  504. spatials := tree.NearestNeighbors(12, rtreego.Point{queryPosition.Lat, queryPosition.Lon})
  505. queryables = make([]QueryableV4, len(spatials))
  506. for i, spatial := range spatials {
  507. queryables[i] = spatial.(StopV2)
  508. }
  509. return queryables
  510. }
  511. func CreateSuccessLocatables(locatables []traffic.Locatable, context traffic.Context, t *traffic.Traffic, other LocatablesResponse) (LocatablesResponse, error) {
  512. success := LocatablesResponseV1{
  513. Locatables: []LocatableV1{},
  514. }
  515. for _, locatable := range locatables {
  516. if stop, ok := locatable.(traffic.Stop); ok {
  517. s := convertTrafficStop(stop)
  518. success.Locatables = append(success.Locatables, LocatableV1(s))
  519. } else if vehicle, ok := locatable.(traffic.VehicleStatus); ok {
  520. v, err := convertTrafficVehicle(vehicle, context, t)
  521. if err != nil {
  522. return success, fmt.Errorf("while converting Traffic Vehicle %s: %w", vehicle.VehicleID, err)
  523. }
  524. success.Locatables = append(success.Locatables, LocatableV1(v))
  525. }
  526. }
  527. if otherV1, ok := other.(LocatablesResponseV1); ok {
  528. success.Locatables = append(success.Locatables, otherV1.Locatables...)
  529. } else {
  530. return success, errors.New("wrong version of other")
  531. }
  532. return success, nil
  533. }
  534. func CreateSuccessLocatablesV2(locatables []traffic.Locatable, context traffic.Context, t *traffic.Traffic, other LocatablesResponse) (LocatablesResponse, error) {
  535. success := LocatablesResponseV2{
  536. Locatables: []LocatableV2{},
  537. }
  538. for _, locatable := range locatables {
  539. if stop, ok := locatable.(traffic.Stop); ok {
  540. s := convertTrafficStopV2(stop, context.FeedID)
  541. success.Locatables = append(success.Locatables, LocatableV2(s))
  542. } else if vehicle, ok := locatable.(traffic.VehicleStatus); ok {
  543. v, err := convertTrafficVehicleV2(vehicle, context, t)
  544. if err != nil {
  545. return success, fmt.Errorf("while converting Traffic Vehicle %s: %w", vehicle.VehicleID, err)
  546. }
  547. success.Locatables = append(success.Locatables, LocatableV2(v))
  548. }
  549. }
  550. if otherV2, ok := other.(LocatablesResponseV2); ok {
  551. success.Locatables = append(success.Locatables, otherV2.Locatables...)
  552. } else {
  553. return success, errors.New("wrong version of other")
  554. }
  555. return success, nil
  556. }
  557. func CreateSuccessLocatablesV3(locatables []traffic.Locatable, context traffic.Context, t *traffic.Traffic, other LocatablesResponse) (LocatablesResponse, error) {
  558. success := LocatablesResponseV3{
  559. Locatables: []LocatableV3{},
  560. }
  561. for _, locatable := range locatables {
  562. if stop, ok := locatable.(traffic.Stop); ok {
  563. s := convertTrafficStopV2(stop, context.FeedID)
  564. success.Locatables = append(success.Locatables, LocatableV3(s))
  565. } else if vehicle, ok := locatable.(traffic.VehicleStatus); ok {
  566. v, err := convertTrafficVehicleV3(vehicle, context, t)
  567. if err != nil {
  568. return success, fmt.Errorf("while converting Traffic Vehicle %s: %w", vehicle.VehicleID, err)
  569. }
  570. success.Locatables = append(success.Locatables, LocatableV3(v))
  571. }
  572. }
  573. if otherV3, ok := other.(LocatablesResponseV3); ok {
  574. success.Locatables = append(success.Locatables, otherV3.Locatables...)
  575. } else {
  576. return success, errors.New("wrong version of other")
  577. }
  578. return success, nil
  579. }
  580. func convertVehicleStatusV1(status traffic.DepartureStatus, timeToArrival float64) VehicleStatusV1 {
  581. if status == traffic.AT_STOP && timeToArrival < 1 { // FIXME this shouldn't happen, this should be caught in gtfs-rt processing
  582. return STATUS_AT_STOP
  583. } else if timeToArrival < 0 {
  584. return STATUS_DEPARTED
  585. } else if timeToArrival < 1 {
  586. return STATUS_INCOMING
  587. }
  588. return STATUS_IN_TRANSIT
  589. }
  590. func convertCongestionLevelV1(level traffic.CongestionLevel) CongestionLevelV1 {
  591. switch level {
  592. case traffic.CONGESTION_UNKNOWN:
  593. return CONGESTION_UNKNOWN
  594. case traffic.CONGESTION_SMOOTH:
  595. return CONGESTION_SMOOTH
  596. case traffic.CONGESTION_STOP_AND_GO:
  597. return CONGESTION_STOP_AND_GO
  598. case traffic.CONGESTION_SIGNIFICANT:
  599. return CONGESTION_SIGNIFICANT
  600. case traffic.CONGESTION_SEVERE:
  601. return CONGESTION_SEVERE
  602. default:
  603. return CONGESTION_UNKNOWN
  604. }
  605. }
  606. func convertOccupancyStatusV1(status traffic.OccupancyStatus) OccupancyStatusV1 {
  607. switch status {
  608. case traffic.OCCUPANCY_UNKNOWN:
  609. return OCCUPANCY_UNKNOWN
  610. case traffic.OCCUPANCY_EMPTY:
  611. return OCCUPANCY_EMPTY
  612. case traffic.OCCUPANCY_MANY_AVAILABLE:
  613. return OCCUPANCY_MANY_AVAILABLE
  614. case traffic.OCCUPANCY_FEW_AVAILABLE:
  615. return OCCUPANCY_FEW_AVAILABLE
  616. case traffic.OCCUPANCY_STANDING_ONLY:
  617. return OCCUPANCY_STANDING_ONLY
  618. case traffic.OCCUPANCY_CRUSHED:
  619. return OCCUPANCY_CRUSHED
  620. case traffic.OCCUPANCY_FULL:
  621. return OCCUPANCY_FULL
  622. case traffic.OCCUPANCY_NOT_ACCEPTING:
  623. return OCCUPANCY_NOT_ACCEPTING
  624. default:
  625. return OCCUPANCY_UNKNOWN
  626. }
  627. }
  628. func CreateSuccessDeparturesV1(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.SpecificAlert, ctx traffic.Context, t *traffic.Traffic, accept map[uint]struct{}, preferredLanguages []language.Tag) (DeparturesResponse, error) {
  629. d := []DepartureV1{}
  630. var success DeparturesResponse
  631. now := time.Now()
  632. timezone, err := traffic.GetTimezone(stop, t, ctx.FeedID)
  633. if err != nil {
  634. return success, err
  635. }
  636. datetime := time.Date(date.Year(), date.Month(),
  637. date.Day(), now.Hour(), now.Minute(), now.Second(), 0, now.Location()).In(timezone)
  638. for _, trafficDeparture := range departures {
  639. zoneAbbr := trafficDeparture.Time.Location().String()
  640. stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence)
  641. if err != nil {
  642. return success, err
  643. }
  644. vehicle, err := convertTrafficVehicle(trafficDeparture.Update.VehicleStatus, ctx, t)
  645. if err != nil {
  646. return success, fmt.Errorf("while converting vehicle status: %w", err)
  647. }
  648. departureTime := traffic.GetTimeWithDelay(trafficDeparture)
  649. departure := DepartureV1{
  650. Id: stopOrder,
  651. Time: TimeV1{
  652. DayOffset: getDayOffset(datetime, departureTime),
  653. Hour: uint8(departureTime.Hour()),
  654. Minute: uint8(departureTime.Minute()),
  655. Second: uint8(departureTime.Second()),
  656. Zone: zoneAbbr,
  657. },
  658. Status: STATUS_IN_TRANSIT,
  659. IsRealtime: trafficDeparture.Update.TimetableRelationship != traffic.NO_TRIP_DATA && trafficDeparture.Update.TimetableRelationship != traffic.NOT_REALTIME,
  660. Vehicle: vehicle,
  661. Boarding: makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff),
  662. }
  663. timeToArrival := departureTime.Sub(datetime).Minutes()
  664. if departure.IsRealtime {
  665. departure.Status = convertVehicleStatusV1(trafficDeparture.Update.VehicleStatus.Status, timeToArrival)
  666. }
  667. d = append(d, departure)
  668. }
  669. success = DeparturesResponseV1{
  670. Stop: convertTrafficStop(stop),
  671. Departures: d,
  672. Alerts: convertTrafficAlerts(alerts),
  673. }
  674. return success, nil
  675. }
  676. func CreateSuccessDeparturesV2(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.SpecificAlert, ctx traffic.Context, t *traffic.Traffic, accept map[uint]struct{}, preferredLanguages []language.Tag) (DeparturesResponse, error) {
  677. d := []DepartureV2{}
  678. var success DeparturesResponse
  679. now := time.Now()
  680. timezone, err := traffic.GetTimezone(stop, t, ctx.FeedID)
  681. if err != nil {
  682. return success, err
  683. }
  684. datetime := time.Date(date.Year(), date.Month(),
  685. date.Day(), now.Hour(), now.Minute(), now.Second(), 0, now.Location()).In(timezone)
  686. for _, trafficDeparture := range departures {
  687. zoneAbbr := trafficDeparture.Time.Location().String()
  688. stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence)
  689. if err != nil {
  690. return success, err
  691. }
  692. vehicle, err := convertTrafficVehicleV2(trafficDeparture.Update.VehicleStatus, ctx, t)
  693. if err != nil {
  694. return success, fmt.Errorf("while converting vehicle status: %w", err)
  695. }
  696. departureTime := traffic.GetTimeWithDelay(trafficDeparture)
  697. departure := DepartureV2{
  698. Id: stopOrder,
  699. Time: TimeV1{
  700. DayOffset: getDayOffset(datetime, departureTime),
  701. Hour: uint8(departureTime.Hour()),
  702. Minute: uint8(departureTime.Minute()),
  703. Second: uint8(departureTime.Second()),
  704. Zone: zoneAbbr,
  705. },
  706. Status: STATUS_IN_TRANSIT,
  707. IsRealtime: trafficDeparture.Update.TimetableRelationship != traffic.NO_TRIP_DATA && trafficDeparture.Update.TimetableRelationship != traffic.NOT_REALTIME,
  708. Vehicle: vehicle,
  709. Boarding: makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff),
  710. }
  711. timeToArrival := departureTime.Sub(datetime).Minutes()
  712. if departure.IsRealtime {
  713. departure.Status = convertVehicleStatusV1(trafficDeparture.Update.VehicleStatus.Status, timeToArrival)
  714. }
  715. d = append(d, departure)
  716. }
  717. success = DeparturesResponseV2{
  718. Stop: convertTrafficStopV2(stop, ctx.FeedID),
  719. Departures: d,
  720. Alerts: convertTrafficAlerts(alerts),
  721. }
  722. return success, nil
  723. }
  724. func CreateSuccessDeparturesV3(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.SpecificAlert, ctx traffic.Context, t *traffic.Traffic, accept map[uint]struct{}, preferredLanguages []language.Tag) (DeparturesResponse, error) {
  725. d := []DepartureV3{}
  726. var success DeparturesResponse
  727. now := time.Now()
  728. timezone, err := traffic.GetTimezone(stop, t, ctx.FeedID)
  729. if err != nil {
  730. return success, err
  731. }
  732. datetime := time.Date(date.Year(), date.Month(),
  733. date.Day(), now.Hour(), now.Minute(), now.Second(), 0, now.Location()).In(timezone)
  734. for _, trafficDeparture := range departures {
  735. zoneAbbr := trafficDeparture.Time.Location().String()
  736. stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence)
  737. if err != nil {
  738. return success, err
  739. }
  740. vehicle, err := convertTrafficVehicleV3(trafficDeparture.Update.VehicleStatus, ctx, t)
  741. if err != nil {
  742. return success, fmt.Errorf("while converting vehicle status: %w", err)
  743. }
  744. departureTime := traffic.GetTimeWithDelay(trafficDeparture)
  745. departure := DepartureV3{
  746. Id: stopOrder,
  747. Time: TimeV1{
  748. DayOffset: getDayOffset(datetime, departureTime),
  749. Hour: uint8(departureTime.Hour()),
  750. Minute: uint8(departureTime.Minute()),
  751. Second: uint8(departureTime.Second()),
  752. Zone: zoneAbbr,
  753. },
  754. Status: STATUS_IN_TRANSIT,
  755. IsRealtime: trafficDeparture.Update.TimetableRelationship != traffic.NO_TRIP_DATA && trafficDeparture.Update.TimetableRelationship != traffic.NOT_REALTIME,
  756. Vehicle: vehicle,
  757. Boarding: makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff),
  758. }
  759. timeToArrival := departureTime.Sub(datetime).Minutes()
  760. if departure.IsRealtime {
  761. departure.Status = convertVehicleStatusV1(trafficDeparture.Update.VehicleStatus.Status, timeToArrival)
  762. }
  763. d = append(d, departure)
  764. }
  765. success = DeparturesResponseV3{
  766. Stop: convertTrafficStopV2(stop, ctx.FeedID),
  767. Departures: d,
  768. Alerts: convertTrafficAlerts(alerts),
  769. }
  770. return success, nil
  771. }
  772. func CreateSuccessDeparturesV4(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.SpecificAlert, ctx traffic.Context, t *traffic.Traffic, accept map[uint]struct{}, preferredLanguages []language.Tag) (DeparturesResponse, error) {
  773. d := []DepartureV4{}
  774. var success DeparturesResponse
  775. now := time.Now()
  776. timezone, err := traffic.GetTimezone(stop, t, ctx.FeedID)
  777. if err != nil {
  778. return success, err
  779. }
  780. datetime := time.Date(date.Year(), date.Month(),
  781. date.Day(), now.Hour(), now.Minute(), now.Second(), 0, now.Location()).In(timezone)
  782. for _, trafficDeparture := range departures {
  783. zoneAbbr := trafficDeparture.Time.Location().String()
  784. stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence)
  785. if err != nil {
  786. return success, err
  787. }
  788. vehicle, err := convertTrafficVehicleV3(trafficDeparture.Update.VehicleStatus, ctx, t)
  789. if err != nil {
  790. return success, fmt.Errorf("while converting vehicle status: %w", err)
  791. }
  792. departureTime := traffic.GetTimeWithDelay(trafficDeparture)
  793. departure := DepartureV4{
  794. Id: stopOrder,
  795. Time: TimeV1{
  796. DayOffset: getDayOffset(datetime, departureTime),
  797. Hour: uint8(departureTime.Hour()),
  798. Minute: uint8(departureTime.Minute()),
  799. Second: uint8(departureTime.Second()),
  800. Zone: zoneAbbr,
  801. },
  802. Status: STATUS_IN_TRANSIT,
  803. IsRealtime: trafficDeparture.Update.TimetableRelationship != traffic.NO_TRIP_DATA && trafficDeparture.Update.TimetableRelationship != traffic.NOT_REALTIME,
  804. Vehicle: vehicle,
  805. Boarding: makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff),
  806. Alerts: convertTrafficAlerts(trafficDeparture.Alerts),
  807. }
  808. timeToArrival := departureTime.Sub(datetime).Minutes()
  809. if departure.IsRealtime {
  810. departure.Status = convertVehicleStatusV1(trafficDeparture.Update.VehicleStatus.Status, timeToArrival)
  811. }
  812. d = append(d, departure)
  813. }
  814. success = DeparturesResponseV4{
  815. Stop: convertTrafficStopV2(stop, ctx.FeedID),
  816. Departures: d,
  817. Alerts: convertTrafficAlerts(alerts),
  818. }
  819. return success, nil
  820. }
  821. func CreateSuccessDeparturesDev(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.SpecificAlert, ctx traffic.Context, t *traffic.Traffic, accept map[uint]struct{}, preferredLanguages []language.Tag) (DeparturesResponse, error) {
  822. d := []DepartureV4{}
  823. var success DeparturesResponse
  824. now := time.Now()
  825. timezone, err := traffic.GetTimezone(stop, t, ctx.FeedID)
  826. if err != nil {
  827. return success, err
  828. }
  829. datetime := time.Date(date.Year(), date.Month(),
  830. date.Day(), now.Hour(), now.Minute(), now.Second(), 0, now.Location()).In(timezone)
  831. for _, trafficDeparture := range departures {
  832. zoneAbbr := trafficDeparture.Time.Location().String()
  833. stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence)
  834. if err != nil {
  835. return success, err
  836. }
  837. vehicle, err := convertTrafficVehicleV3(trafficDeparture.Update.VehicleStatus, ctx, t)
  838. if err != nil {
  839. return success, fmt.Errorf("while converting vehicle status: %w", err)
  840. }
  841. departureTime := traffic.GetTimeWithDelay(trafficDeparture)
  842. departure := DepartureV4{
  843. Id: stopOrder,
  844. Time: TimeV1{
  845. DayOffset: getDayOffset(datetime, departureTime),
  846. Hour: uint8(departureTime.Hour()),
  847. Minute: uint8(departureTime.Minute()),
  848. Second: uint8(departureTime.Second()),
  849. Zone: zoneAbbr,
  850. },
  851. Status: STATUS_IN_TRANSIT,
  852. IsRealtime: trafficDeparture.Update.TimetableRelationship != traffic.NO_TRIP_DATA && trafficDeparture.Update.TimetableRelationship != traffic.NOT_REALTIME,
  853. Vehicle: vehicle,
  854. Boarding: makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff),
  855. Alerts: convertTrafficAlerts(trafficDeparture.Alerts),
  856. }
  857. timeToArrival := departureTime.Sub(datetime).Minutes()
  858. if departure.IsRealtime {
  859. departure.Status = convertVehicleStatusV1(trafficDeparture.Update.VehicleStatus.Status, timeToArrival)
  860. }
  861. d = append(d, departure)
  862. }
  863. success = DeparturesResponseDev{
  864. Stop: convertTrafficStopV2(stop, ctx.FeedID),
  865. Departures: d,
  866. Alerts: convertTrafficAlerts(alerts),
  867. }
  868. return success, nil
  869. }
  870. func fromColor(c traffic.Colour) ColourV1 {
  871. return ColourV1{
  872. R: c.R,
  873. G: c.G,
  874. B: c.B,
  875. }
  876. }
  877. func makeLineTypeV1(line traffic.Line) LineTypeV1 {
  878. if line.Kind == traffic.TRAM {
  879. return TRAM
  880. } else if line.Kind == traffic.BUS {
  881. return BUS
  882. } else {
  883. return LINE_UNKNOWN
  884. }
  885. }
  886. func makeLineTypeV2(line traffic.Line) LineTypeV2 {
  887. if line.Kind == traffic.TRAM {
  888. return LINE_V2_TRAM
  889. } else if line.Kind == traffic.BUS {
  890. return LINE_V2_BUS
  891. } else if line.Kind == traffic.TROLLEYBUS {
  892. return LINE_V2_TROLLEYBUS
  893. } else {
  894. return LINE_V2_UNKNOWN
  895. }
  896. }
  897. func makeLineTypeV3(line traffic.Line) LineTypeV3 {
  898. switch line.Kind {
  899. case traffic.TRAM:
  900. return LINE_V3_TRAM
  901. case traffic.BUS:
  902. return LINE_V3_BUS
  903. case traffic.TROLLEYBUS:
  904. return LINE_V3_TROLLEYBUS
  905. case traffic.METRO:
  906. return LINE_V3_METRO
  907. case traffic.RAIL:
  908. return LINE_V3_RAIL
  909. case traffic.FERRY:
  910. return LINE_V3_FERRY
  911. case traffic.CABLE_TRAM:
  912. return LINE_V3_CABLE_TRAM
  913. case traffic.CABLE_CAR:
  914. return LINE_V3_CABLE_CAR
  915. case traffic.FUNICULAR:
  916. return LINE_V3_FUNICULAR
  917. case traffic.MONORAIL:
  918. return LINE_V3_MONORAIL
  919. default:
  920. return LINE_V3_UNKNOWN
  921. }
  922. }
  923. func makeChangeOptionV1(option traffic.ChangeOption) ChangeOptionV1 {
  924. return ChangeOptionV1{
  925. LineName: option.LineName,
  926. Headsign: option.Headsign,
  927. }
  928. }
  929. func makeBoardingV1(pickup, dropoff traffic.Boarding) uint8 {
  930. b := BOARDING_NONE
  931. if pickup == traffic.REGULAR {
  932. b |= ONBOARDING_REGULAR
  933. } else if pickup == traffic.BY_PHONE {
  934. b |= ONBOARDING_PHONE
  935. } else if pickup == traffic.BY_DRIVER {
  936. b |= ONBOARDING_DRIVER
  937. }
  938. if dropoff == traffic.REGULAR {
  939. b |= OFFBOARDING_REGULAR
  940. } else if dropoff == traffic.BY_PHONE {
  941. b |= OFFBOARDING_PHONE
  942. } else if dropoff == traffic.BY_DRIVER {
  943. b |= OFFBOARDING_DRIVER
  944. }
  945. return uint8(b)
  946. }
  947. func makeAlertCauseV1(alert traffic.SpecificAlert) AlertCauseV1 {
  948. switch alert.Cause {
  949. case 0:
  950. return CAUSE_UNKNOWN
  951. case 1:
  952. return CAUSE_OTHER
  953. case 2:
  954. return CAUSE_TECHNICAL_PROBLEM
  955. case 3:
  956. return CAUSE_STRIKE
  957. case 4:
  958. return CAUSE_DEMONSTRATION
  959. case 5:
  960. return CAUSE_ACCIDENT
  961. case 6:
  962. return CAUSE_HOLIDAY
  963. case 7:
  964. return CAUSE_WEATHER
  965. case 8:
  966. return CAUSE_MAINTENANCE
  967. case 9:
  968. return CAUSE_CONSTRUCTION
  969. case 10:
  970. return CAUSE_POLICE_ACTIVITY
  971. case 11:
  972. return CAUSE_MEDICAL_EMERGENCY
  973. default:
  974. return CAUSE_UNKNOWN
  975. }
  976. }
  977. func makeAlertEffectV1(alert traffic.SpecificAlert) AlertEffectV1 {
  978. switch alert.Effect {
  979. case 0:
  980. return EFFECT_NO_SERVICE
  981. case 1:
  982. return EFFECT_REDUCED_SERVICE
  983. case 2:
  984. return EFFECT_SIGNIFICANT_DELAYS
  985. case 3:
  986. return EFFECT_DETOUR
  987. case 4:
  988. return EFFECT_ADDITIONAL_SERVICE
  989. case 5:
  990. return EFFECT_MODIFIED_SERVICE
  991. case 6:
  992. return EFFECT_OTHER
  993. case 7:
  994. return EFFECT_UNKNOWN
  995. case 8:
  996. return EFFECT_STOP_MOVED
  997. case 9:
  998. return EFFECT_NONE
  999. case 10:
  1000. return EFFECT_ACCESSIBILITY_ISSUE
  1001. default:
  1002. return EFFECT_UNKNOWN
  1003. }
  1004. }