lyon-transports/tcl_stop.go

224 lines
5.4 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"github.com/carlmjohnson/requests"
"net/http"
"regexp"
"slices"
"time"
)
type GrandLyonConfig struct {
Username string
Password string
Client *http.Client
}
type Stop struct {
ID int `json:"id" example:"290"`
Name string `json:"name" example:"Grange Blanche"`
}
func NewStop(tclStop TCLStop) Stop {
return Stop{
ID: tclStop.Id,
Name: tclStop.Nom,
}
}
type Passage struct {
Ligne string `json:"ligne" example:"49A"`
Delays []string `json:"delays" example:"53 min"`
Destination Stop `json:"destination"`
}
type Passages struct {
Passages []Passage `json:"passages"`
Stop Stop `json:"stop"`
}
type delay int
func (d delay) String() string {
switch d {
case -2:
return "Passé"
case -1:
return "Proche"
default:
return fmt.Sprintf("%d min", d)
}
}
var errNoPassageFound = errors.New("no passage found")
func getPassages(ctx context.Context, config GrandLyonConfig, now func() time.Time, stopID int) (*Passages, error) {
client := config.Client
if client == nil {
client = &http.Client{}
}
var tclPassages TCLPassages
err := requests.URL("https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tclpassagearret/all.json?maxfeatures=-1").
Client(client).
BasicAuth(config.Username, config.Password).
ToJSON(&tclPassages).
Fetch(ctx)
if err != nil {
return nil, fmt.Errorf("fetching passages: %w", err)
}
type passageKey struct {
line string
destination int
}
stops := map[int]TCLStop{stopID: {}}
passages := make(map[passageKey][]delay)
for _, passage := range tclPassages.Values {
if passage.Id != stopID || passage.Type != "E" {
continue
}
// Remove letter suffix to group by commercial line name
line := regexp.MustCompile("[A-Z]$").ReplaceAllString(passage.Ligne, "")
destination := passage.Idtarretdestination
stops[destination] = TCLStop{}
key := passageKey{line: line, destination: destination}
delays := passages[key]
delays = append(delays, getDelay(passage.Heurepassage, now()))
passages[key] = delays
}
if len(passages) == 0 {
return nil, errNoPassageFound
}
var tclStops TCLStops
err = requests.URL("https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tclarret/all.json?maxfeatures=-1").
Client(client).
BasicAuth(config.Username, config.Password).
ToJSON(&tclStops).
Fetch(ctx)
if err != nil {
return nil, fmt.Errorf("fetching stops: %w", err)
}
updated := 0
for _, stop := range tclStops.Values {
if _, stopToUpdate := stops[stop.Id]; stopToUpdate {
stops[stop.Id] = stop
updated++
}
if updated == len(stops) {
break
}
}
resPassages := make([]Passage, 0, len(passages))
for key, delays := range passages {
slices.Sort(delays)
delaysStr := make([]string, len(delays))
for i, delay := range delays {
delaysStr[i] = delay.String()
}
resPassages = append(resPassages, Passage{
Ligne: key.line,
Delays: delaysStr,
Destination: NewStop(stops[key.destination]),
})
}
slices.SortFunc(resPassages, func(a, b Passage) int {
if a.Ligne < b.Ligne {
return -1
}
return 1
})
return &Passages{
Passages: resPassages,
Stop: NewStop(stops[stopID]),
}, nil
}
func getDelay(heurepassage string, now time.Time) delay {
location, err := time.LoadLocation("Europe/Paris")
if err != nil {
location = time.UTC
}
passage, err := time.ParseInLocation("2006-01-02 15:04:05", heurepassage, location)
if err != nil {
return delay(-1)
}
if passage.Before(now) {
return delay(-2)
}
dur := passage.Sub(now)
minutes := int(dur.Minutes())
if minutes <= 0 {
return delay(-1)
}
return delay(minutes)
}
type TCLPassage struct {
Coursetheorique string `json:"coursetheorique"`
Delaipassage string `json:"delaipassage"`
Direction string `json:"direction"`
Gid int `json:"gid"`
Heurepassage string `json:"heurepassage"`
Id int `json:"id"`
Idtarretdestination int `json:"idtarretdestination"`
LastUpdateFme string `json:"last_update_fme"`
Ligne string `json:"ligne"`
Type string `json:"type"`
}
type TCLPassages struct {
Fields []string `json:"fields"`
LayerName string `json:"layer_name"`
NbResults int `json:"nb_results"`
TableAlias interface{} `json:"table_alias"`
TableHref string `json:"table_href"`
Values []TCLPassage `json:"values"`
}
type TCLStop struct {
Adresse string `json:"adresse"`
Ascenseur bool `json:"ascenseur"`
Commune *string `json:"commune"`
Desserte string `json:"desserte"`
Escalator bool `json:"escalator"`
Gid int `json:"gid"`
Id int `json:"id"`
Insee *string `json:"insee"`
LastUpdate string `json:"last_update"`
LastUpdateFme string `json:"last_update_fme"`
Lat float64 `json:"lat"`
LocaliseFaceAAdresse bool `json:"localise_face_a_adresse"`
Lon float64 `json:"lon"`
Nom string `json:"nom"`
Pmr bool `json:"pmr"`
}
type TCLStops struct {
Fields []string `json:"fields"`
LayerName string `json:"layer_name"`
NbResults int `json:"nb_results"`
TableAlias interface{} `json:"table_alias"`
TableHref string `json:"table_href"`
Values []TCLStop `json:"values"`
}