mirror of
https://github.com/Crocmagnon/lyon-transports.git
synced 2024-11-21 13:38:06 +01:00
initial revision
This commit is contained in:
commit
fb8a40579b
8 changed files with 385627 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.idea
|
18
go.mod
Normal file
18
go.mod
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
module github.com/Crocmagnon/lyon-transports
|
||||||
|
|
||||||
|
go 1.23.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/carlmjohnson/requests v0.24.2
|
||||||
|
github.com/danielgtaylor/huma/v2 v2.22.1
|
||||||
|
github.com/jarcoal/httpmock v1.3.1
|
||||||
|
gotest.tools/v3 v3.5.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/spf13/cobra v1.8.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
golang.org/x/net v0.27.0 // indirect
|
||||||
|
)
|
35
go.sum
Normal file
35
go.sum
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
github.com/carlmjohnson/requests v0.24.2 h1:JDakhAmTIKL/qL/1P7Kkc2INGBJIkIFP6xUeUmPzLso=
|
||||||
|
github.com/carlmjohnson/requests v0.24.2/go.mod h1:duYA/jDnyZ6f3xbcF5PpZ9N8clgopubP2nK5i6MVMhU=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/danielgtaylor/huma/v2 v2.22.1 h1:fXhyjGSj5u5VeI+laa+e+7OxiQsP9RC55/tWZZvI4YA=
|
||||||
|
github.com/danielgtaylor/huma/v2 v2.22.1/go.mod h1:2NZmGf/A+SstJYQlq0Xp4nsTDCmPvKS2w9vI8c9sf1A=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
|
||||||
|
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||||
|
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
||||||
|
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
|
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
95
main.go
Normal file
95
main.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/danielgtaylor/huma/v2"
|
||||||
|
"github.com/danielgtaylor/huma/v2/adapters/humago"
|
||||||
|
"github.com/danielgtaylor/huma/v2/humacli"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Host string `help:"Host to listen to" default:"127.0.0.1"`
|
||||||
|
Port int `help:"Port to listen on" default:"8888"`
|
||||||
|
GrandLyonUsername string `help:"Grand Lyon username" short:"u" required:"true"`
|
||||||
|
GrandLyonPassword string `help:"Grand Lyon password" short:"p" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type statusOutput struct {
|
||||||
|
Body struct {
|
||||||
|
Status string `json:"status" example:"ok" doc:"API status"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stopOutput struct {
|
||||||
|
Body Passages
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRoutes(api huma.API, glConfig GrandLyonConfig, now func() time.Time) {
|
||||||
|
huma.Register(api, huma.Operation{
|
||||||
|
OperationID: "healthcheck",
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/",
|
||||||
|
Summary: "Get API status",
|
||||||
|
Description: "Get the status of the API.",
|
||||||
|
}, func(ctx context.Context, input *struct{}) (*statusOutput, error) {
|
||||||
|
resp := &statusOutput{}
|
||||||
|
resp.Body.Status = "ok"
|
||||||
|
return resp, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
huma.Get(api, "/tcl/stop/{stopID}", func(ctx context.Context, input *struct {
|
||||||
|
StopID int `path:"stopID" doc:"Stop id to monitor. Can be obtained using https://data.grandlyon.com/jeux-de-donnees/points-arret-reseau-transports-commun-lyonnais/donnees"`
|
||||||
|
}) (*stopOutput, error) {
|
||||||
|
passages, err := getPassages(ctx, glConfig, now, input.StopID)
|
||||||
|
if errors.Is(err, errNoPassageFound) {
|
||||||
|
return nil, huma.NewError(http.StatusNotFound, "no passage found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &stopOutput{Body: *passages}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create a CLI app which takes a port option.
|
||||||
|
cli := humacli.New(func(hooks humacli.Hooks, options *Options) {
|
||||||
|
// Create a new router & API
|
||||||
|
router := http.NewServeMux()
|
||||||
|
api := humago.New(router, huma.DefaultConfig("My API", "1.0.0"))
|
||||||
|
server := http.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", options.Host, options.Port),
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
|
||||||
|
glConfig := GrandLyonConfig{
|
||||||
|
Username: options.GrandLyonUsername,
|
||||||
|
Password: options.GrandLyonPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
addRoutes(api, glConfig, time.Now)
|
||||||
|
|
||||||
|
hooks.OnStart(func() {
|
||||||
|
fmt.Printf("Starting server on %s...\n", server.Addr)
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
fmt.Printf("Error running server: %s\n", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
hooks.OnStop(func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
fmt.Printf("Error shutting down server: %s\n", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cli.Run()
|
||||||
|
}
|
88
main_test.go
Normal file
88
main_test.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/danielgtaylor/huma/v2/humatest"
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetStatus(t *testing.T) {
|
||||||
|
_, api := humatest.New(t)
|
||||||
|
|
||||||
|
addRoutes(api, GrandLyonConfig{}, nil)
|
||||||
|
|
||||||
|
resp := api.Get("/")
|
||||||
|
assert.Equal(t, resp.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetStop(t *testing.T) {
|
||||||
|
_, api := humatest.New(t)
|
||||||
|
|
||||||
|
transport := httpmock.NewMockTransport()
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
config := GrandLyonConfig{
|
||||||
|
Client: client,
|
||||||
|
}
|
||||||
|
now := func() time.Time {
|
||||||
|
location, err := time.LoadLocation("Europe/Paris")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Could not load Europe/Paris")
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Date(2022, 8, 25, 8, 23, 10, 0, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
transport.RegisterResponder(http.MethodGet,
|
||||||
|
"https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tclarret/all.json?maxfeatures=-1",
|
||||||
|
httpmock.NewBytesResponder(http.StatusOK, httpmock.File("./testdata/stops.json").Bytes()))
|
||||||
|
transport.RegisterResponder(http.MethodGet,
|
||||||
|
"https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tclpassagearret/all.json?maxfeatures=-1",
|
||||||
|
httpmock.NewBytesResponder(http.StatusOK, httpmock.File("./testdata/passages.json").Bytes()))
|
||||||
|
|
||||||
|
addRoutes(api, config, now)
|
||||||
|
|
||||||
|
t.Run("stop not found", func(t *testing.T) {
|
||||||
|
resp := api.Get("/tcl/stop/0")
|
||||||
|
assert.Equal(t, resp.Code, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("stop exists", func(t *testing.T) {
|
||||||
|
resp := api.Get("/tcl/stop/290")
|
||||||
|
assert.Equal(t, resp.Code, http.StatusOK)
|
||||||
|
|
||||||
|
var passages Passages
|
||||||
|
err := json.Unmarshal(resp.Body.Bytes(), &passages)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
assert.DeepEqual(t, passages, Passages{
|
||||||
|
Passages: []Passage{
|
||||||
|
{
|
||||||
|
Ligne: "37",
|
||||||
|
Delays: []string{"Passé", "Proche"},
|
||||||
|
Destination: Stop{
|
||||||
|
ID: 46642,
|
||||||
|
Name: "Charpennes",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Ligne: "C17",
|
||||||
|
Delays: []string{"Proche", "10 min"},
|
||||||
|
Destination: Stop{
|
||||||
|
ID: 46644,
|
||||||
|
Name: "Charpennes",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Stop: Stop{
|
||||||
|
ID: 290,
|
||||||
|
Name: "Buers - Salengro",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
224
tcl_stop.go
Normal file
224
tcl_stop.go
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
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"`
|
||||||
|
}
|
333788
testdata/passages.json
vendored
Normal file
333788
testdata/passages.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
51378
testdata/stops.json
vendored
Normal file
51378
testdata/stops.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue