api.go 39 KB

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