Initial commit

This commit is contained in:
nemunaire 2022-10-21 23:54:17 +02:00
commit 0a854f708a
17 changed files with 796 additions and 0 deletions

30
api/api.go Normal file
View file

@ -0,0 +1,30 @@
package api
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
type apiMetadata struct {
Call string `json:"call"`
Date time.Time `json:"date"`
Version int `json:"version"`
}
type apiResult struct {
Result interface{} `json:"result"`
Metadata apiMetadata `json:"_metadata"`
}
func APIResult(c *gin.Context, res interface{}) apiResult {
return apiResult{
Result: res,
Metadata: apiMetadata{
Call: fmt.Sprintf("%s %s", c.Request.Method, c.Request.URL.Path),
Date: time.Now(),
Version: 4,
},
}
}

9
api/destinations.go Normal file
View file

@ -0,0 +1,9 @@
package api
import (
"github.com/gin-gonic/gin"
)
func declareDestinationsRoutes(route *gin.RouterGroup) {
}

138
api/lines.go Normal file
View file

@ -0,0 +1,138 @@
package api
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
)
type IDFMLine struct {
DatasetID string `json:"datasetid"`
RecordIDs string `json:"recordid"`
Fields struct {
IdLine string `json:"id_line"`
Name string `json:"name_line"`
ShortName string `json:"shortname_line"`
TransportMode string `json:"transportmode"`
TransportSubmode string `json:"transportsubmode"`
Type string `json:"type"`
OperatorRef string `json:"operatorref"`
AdditionalOperators string `json:"additionaloperators,omitempty"`
OperatorName string `json:"operatorname"`
NetworkName string `json:"networkname,omitempty"`
WebColor string `json:"colourweb_hexa"`
PrintColor string `json:"colourprint_cmjn"`
WebTextColor string `json:"textcolourweb_hexa"`
TextPrintColor string `json:"textcolourprint_hexa"`
Accessibility string `json:"accessibility"`
AudibleSignAvailable string `json:"audiblesigns_available"`
VisualSignAvailable string `json:"visualsigns_available"`
NoticeTitle string `json:"notice_title"`
NoticeText string `json:"notice_text"`
Status string `json:"status"`
ShortNameGroup string `json:"shortname_groupoflines"`
IdGroup string `json:"id_groupoflines"`
ExternalCode string `json:"externalcode_line"`
} `json:"fields"`
RecordTimestamp time.Time `json:"record_timestamp"`
}
type PGLine struct {
Code string `json:"code"`
Name string `json:"name"`
Directions string `json:"directions"`
Id string `json:"id"`
}
var IDFMLines []IDFMLine
func init() {
fd, err := os.Open("referentiel-des-lignes.json")
if err != nil {
log.Fatal("Unable to open `referentiel-des-lignes.json`:", err.Error())
}
defer fd.Close()
dec := json.NewDecoder(fd)
if err = dec.Decode(&IDFMLines); err != nil {
log.Fatal("Unable to decode `referentiel-des-lignes.json`:", err.Error())
}
}
func convertLineType(old string) string {
switch old {
case "buses":
return "bus"
case "metros":
return "metro"
case "noctiliens":
return "noctilien"
case "rers":
return "rail"
case "tramways":
return "tram"
default:
return old
}
}
func declareLinesRoutes(router *gin.RouterGroup) {
router.GET("/lines", func(c *gin.Context) {
var modes []string
for _, line := range IDFMLines {
mode := line.Fields.TransportMode
found := false
for _, m := range modes {
if mode == m {
found = true
break
}
}
if !found {
modes = append(modes, mode)
}
}
c.JSON(http.StatusOK, APIResult(c, modes))
})
router.GET("/lines/:type", func(c *gin.Context) {
t := convertLineType(string(c.Param("type")))
var lines []PGLine
for _, line := range IDFMLines {
if line.Fields.TransportMode == t || strings.ToLower(line.Fields.NetworkName) == t {
name := line.Fields.Name
if line.Fields.ShortNameGroup != "" {
if strings.Contains(line.Fields.ShortNameGroup, name) {
name = line.Fields.ShortNameGroup
} else if name == line.Fields.ShortName {
name = fmt.Sprintf("%s - %s", line.Fields.Name, line.Fields.ShortNameGroup)
}
}
pgline := PGLine{
Code: fmt.Sprintf("IDFM:%s", line.Fields.IdLine),
Name: name,
Directions: "",
Id: line.Fields.IdLine,
}
lines = append(lines, pgline)
}
}
c.JSON(http.StatusOK, APIResult(c, map[string][]PGLine{
t: lines,
}))
})
}

9
api/missions.go Normal file
View file

@ -0,0 +1,9 @@
package api
import (
"github.com/gin-gonic/gin"
)
func declareMissionsRoutes(route *gin.RouterGroup) {
}

18
api/routes.go Normal file
View file

@ -0,0 +1,18 @@
package api
import (
"github.com/gin-gonic/gin"
"git.nemunai.re/nemunaire/idfm-api/config"
)
func DeclareRoutes(router *gin.Engine, cfg *config.Config) {
apiRoutes := router.Group("/api")
declareDestinationsRoutes(apiRoutes)
declareLinesRoutes(apiRoutes)
declareMissionsRoutes(apiRoutes)
declareSchedulesRoutes(apiRoutes)
declareStationsRoutes(apiRoutes)
declareTrafficRoutes(apiRoutes)
}

153
api/schedules.go Normal file
View file

@ -0,0 +1,153 @@
package api
import (
"encoding/json"
"flag"
"fmt"
"math"
"net/http"
"net/url"
"time"
"github.com/gin-gonic/gin"
)
const IDFM_BASEURL = "https://prim.iledefrance-mobilites.fr/marketplace"
var IDFM_TOKEN = ""
func init() {
flag.StringVar(&IDFM_TOKEN, "token-IDFM", IDFM_TOKEN, "Token to access IDFM API")
}
type IDFMSchedule struct {
Siri struct {
ServiceDelivery struct {
ResponseTimestamp time.Time `json:"ResponseTimestamp"`
ProducerRef string `json:"ProducerRef"`
ResponseMessageIdentifier string `json:"ResponseMessageIdentifier"`
StopMonitoringDelivery []struct {
ResponseTimestamp time.Time `json:"ResponseTimestamp"`
Version string `json:"Version"`
Status string `json:"Status"`
MonitoredStopVisit []struct {
RecordedAtTime time.Time `json:"RecordedAtTime"`
ItemIdentifier string `json:"ItemIdentifier"`
MonitoringRef struct {
Value string `json:"value"`
} `json:"MonitoringRef"`
MonitoredVehicleJourney struct {
LineRef struct {
Value string `json:"value"`
} `json:"LineRef"`
OperatorRef struct {
Value string `json:"value"`
} `json:"OperatorRef"`
FramedVehicleJourneyRef struct {
DataFrameRef struct {
Value string `json:"value"`
} `json:"DataFrameRef"`
DatedVehicleJourneyRef string `json:"DatedVehicleJourneyRef"`
} `json:"FramedVehicleJourneyRef"`
DirectionName []struct {
Value string `json:"value"`
} `json:"DirectionName"`
DestinationRef struct {
Value string `json:"value"`
} `json:"DestinationRef"`
DestinationName []struct {
Value string `json:"value"`
} `json:"DestinationName"`
JourneyNote []struct {
Value string `json:"value"`
} `json:"JourneyNote"`
MonitoredCall struct {
StopPointName []struct {
Value string `json:"value"`
} `json:"StopPointName"`
VehicleAtStop bool `json:"VehicleAtStop"`
DestinationDisplay []struct {
Value string `json:"value"`
} `json:"DestinationDisplay"`
ExpectedArrivalTime time.Time `json:"ExpectedArrivalTime"`
ExpectedDepartureTime time.Time `json:"ExpectedDepartureTime"`
DepartureStatus string `json:"DepartureStatus"`
} `json:"MonitoredCall"`
} `json:"MonitoredVehicleJourney"`
} `json:"MonitoredStopVisit"`
} `json:"StopMonitoringDelivery"`
} `json:"ServiceDelivery"`
} `json:"siri"`
}
type PGSchedule struct {
Destination string `json:"destionation"`
Message string `json:"message"`
}
func declareSchedulesRoutes(router *gin.RouterGroup) {
router.GET("/schedules/:type/:code/:station/:way", func(c *gin.Context) {
t := string(c.Param("type"))
station := convertLineType(string(c.Param("station")))
rurl, err := url.JoinPath(IDFM_BASEURL, "stop-monitoring")
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
requrl, err := url.Parse(rurl)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
reqquery := url.Values{}
reqquery.Add("MonitoringRef", station)
requrl.RawQuery = reqquery.Encode()
req, err := http.NewRequest("GET", requrl.String(), nil)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
req.Header.Add("Accept", "application/json")
req.Header.Add("apikey", IDFM_TOKEN)
res, err := http.DefaultClient.Do(req)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
defer res.Body.Close()
var schedules IDFMSchedule
dec := json.NewDecoder(res.Body)
if err = dec.Decode(&schedules); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
pgs := []PGSchedule{}
for _, vehicule := range schedules.Siri.ServiceDelivery.StopMonitoringDelivery[0].MonitoredStopVisit {
msg := vehicule.MonitoredVehicleJourney.MonitoredCall.ExpectedDepartureTime.String()
if t == "metros" || t == "buses" || t == "tramways" {
if vehicule.MonitoredVehicleJourney.MonitoredCall.VehicleAtStop {
msg = "À quai"
} else {
msg = fmt.Sprintf("%d mn", int(math.Floor(time.Until(vehicule.MonitoredVehicleJourney.MonitoredCall.ExpectedDepartureTime).Minutes())))
}
}
pgs = append(pgs, PGSchedule{
Destination: vehicule.MonitoredVehicleJourney.MonitoredCall.DestinationDisplay[0].Value,
Message: msg,
})
}
c.JSON(http.StatusOK, pgs)
})
}

73
api/stations.go Normal file
View file

@ -0,0 +1,73 @@
package api
import (
"encoding/json"
"log"
"net/http"
"os"
"github.com/gin-gonic/gin"
)
type IDFMStation struct {
DatasetID string `json:"datasetid"`
RecordIDs string `json:"recordid"`
Fields struct {
Id string `json:"id"`
PointGeo []float64 `json:"pointgeo"`
StopId string `json:"stop_id"`
StopName string `json:"stop_name"`
OperatorName string `json:"operatorname"`
NomCommune string `json:"nom_commune"`
RouteLongName string `json:"route_long_name"`
StopLat string `json:"stop_lat"`
StopLon string `json:"stop_lon"`
CodeINSEE string `json:"code_insee"`
} `json:"fields"`
Geometry struct {
Type string `json:"type"`
Coordinates []float64 `json:"coordinates"`
} `json:"geometry"`
}
type PGStation struct {
Id string `json:"slug"`
Name string `json:"name"`
}
var IDFMStations []IDFMStation
func init() {
fd, err := os.Open("arrets-lignes.json")
if err != nil {
log.Fatal("Unable to open `arrets-lignes.json`:", err.Error())
}
defer fd.Close()
dec := json.NewDecoder(fd)
if err = dec.Decode(&IDFMStations); err != nil {
log.Fatal("Unable to decode `arrets-lignes.json`:", err.Error())
}
}
func declareStationsRoutes(router *gin.RouterGroup) {
router.GET("/stations/:type/:code", func(c *gin.Context) {
code := convertLineType(string(c.Param("code")))
var stations []PGStation
for _, station := range IDFMStations {
if station.Fields.Id == code {
pgstation := PGStation{
Id: station.Fields.StopId,
Name: station.Fields.StopName,
}
stations = append(stations, pgstation)
}
}
c.JSON(http.StatusOK, APIResult(c, map[string][]PGStation{
"stations": stations,
}))
})
}

9
api/traffic.go Normal file
View file

@ -0,0 +1,9 @@
package api
import (
"github.com/gin-gonic/gin"
)
func declareTrafficRoutes(route *gin.RouterGroup) {
}