connectiq-ura/api/main.go

264 lines
5.1 KiB
Go

package main
import (
"bufio"
"encoding/json"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
geo "github.com/kellydunn/golang-geo"
)
var returnList = []string{
"StopPointName",
"StopID",
"StopPointIndicator",
"StopPointState",
"Latitude",
"Longitude",
}
var apiBase string = "http://ivu.aseag.de/interfaces/ura"
type StopPoint struct {
Name string `json:"name"`
ID int `json:"id"`
Indicator string `json:"indicator"`
State bool `json:"state"`
Coordinates *geo.Point `json:"coord"`
Distance float64 `json:"dist"`
Bearing float64 `json:"bearing"`
BearingString string `json:"bearing_str"`
}
func BearingToCardinal(b float64) string {
var card = []string{"N", "NE", "E", "SE", "S", "SW", "W", "NW"}
b += 22.5
if b < 0 {
b += 360
}
b /= 45
return card[int(b)]
}
func (sp *StopPoint) UnmarshalJSON(buf []byte) error {
// Ex: [0,"Bahnhof Rothe Erde","100302","H.1",0,50.7703152,6.1174127]
var rType int
var id string
var state int
var err error
var lat, lng float64
tmp := []interface{}{&rType, &sp.Name, &id, &sp.Indicator, &state, &lat, &lng}
wantLen := len(tmp)
if err := json.Unmarshal(buf, &tmp); err != nil {
return fmt.Errorf("Failed to parse: %s", buf)
}
sp.ID, err = strconv.Atoi(id)
if err != nil {
return nil
}
sp.State = state != 0
sp.Coordinates = geo.NewPoint(lat, lng)
if rType != 0 {
return fmt.Errorf("Invalid response type: %d != 0", rType)
}
if g, e := len(tmp), wantLen; g != e {
return fmt.Errorf("wrong number of fields in Notification: %d != %d", g, e)
}
return nil
}
type Prediction struct {
LineName string `json:"line"`
DestinationName string `json:"dest"`
EstimatedTime time.Time `json:"estimated"`
ExpireTime time.Time `json:"expire"`
Delta int `json:"delta"`
DeltaString string `json:"delta_str"`
}
func (p *Prediction) UnmarshalJSON(buf []byte) error {
// Ex: [1,"33","Vaals Heuvel",1610732635000,1610732880000]
var rType int
var estimated, expired json.Number
tmp := []interface{}{&rType, &p.LineName, &p.DestinationName, &estimated, &expired}
wantLen := len(tmp)
if err := json.Unmarshal(buf, &tmp); err != nil {
return fmt.Errorf("Failed to parse: %s", buf)
}
expiredInt, _ := expired.Int64()
estimatedInt, _ := estimated.Int64()
p.ExpireTime = time.Unix(expiredInt/1000, 0)
p.EstimatedTime = time.Unix(estimatedInt/1000, 0)
if rType != 1 {
return fmt.Errorf("Invalid response type: %d != 1", rType)
}
if g, e := len(tmp), wantLen; g != e {
return fmt.Errorf("wrong number of fields in Notification: %d != %d", g, e)
}
return nil
}
func main() {
r := gin.Default()
r.GET("/stops", func(c *gin.Context) {
latStr := c.Query("latitude")
lngStr := c.Query("longitude")
distStr := c.Query("distance")
limitStr := c.Query("limit")
var lat, lng float64
var dist float64
var err error
var limit int
lat, err = strconv.ParseFloat(latStr, 64)
if err != nil {
}
lng, err = strconv.ParseFloat(lngStr, 64)
if err != nil {
}
point := geo.NewPoint(lat, lng)
dist, err = strconv.ParseFloat(distStr, 64)
if err != nil {
}
limit, err = strconv.Atoi(limitStr)
if err != nil {
}
res, _ := http.Get(apiBase + "/instant_V1" +
fmt.Sprintf("?Circle=%f,%f,%f", point.Lat(), point.Lng(), dist) +
"&StopPointState=0" +
"&ReturnList=" + strings.Join(returnList, ","))
var sps = []StopPoint{}
scanner := bufio.NewScanner(res.Body)
if !scanner.Scan() {
}
_ = scanner.Bytes()
for scanner.Scan() {
line := scanner.Bytes()
var sp StopPoint
err = json.Unmarshal(line, &sp)
if err != nil {
continue
}
sp.Distance = point.GreatCircleDistance(sp.Coordinates) * 1000
sp.Bearing = point.BearingTo(sp.Coordinates)
sp.BearingString = BearingToCardinal(sp.Bearing)
sps = append(sps, sp)
}
sort.Slice(sps, func(a, b int) bool {
return sps[a].Distance < sps[b].Distance
})
if limit > len(sps) || limit == 0 {
limit = len(sps)
}
c.JSON(200, sps[:limit])
})
r.GET("/departures", func(c *gin.Context) {
var err error
var limit int
idStr := c.Query("id")
limitStr := c.Query("limit")
id, err := strconv.Atoi(idStr)
if err != nil {
}
limit, err = strconv.Atoi(limitStr)
if err != nil {
}
res, _ := http.Get(apiBase + "/instant_V1" +
fmt.Sprintf("?StopID=%d", id) +
"&ReturnList=LineName,DestinationName,EstimatedTime,ExpireTime")
var ps = []Prediction{}
scanner := bufio.NewScanner(res.Body)
if !scanner.Scan() {
}
_ = scanner.Bytes()
for scanner.Scan() {
line := scanner.Bytes()
var p Prediction
err = json.Unmarshal(line, &p)
if err != nil {
continue
}
delta := time.Until(p.EstimatedTime)
p.Delta = int(delta / 1e9) // to secs
p.DeltaString = delta.Truncate(time.Second).String()
ps = append(ps, p)
}
sort.Slice(ps, func(a, b int) bool {
return ps[a].EstimatedTime.Before(ps[b].EstimatedTime)
})
if limit > len(ps) || limit == 0 {
limit = len(ps)
}
c.JSON(200, ps[:limit])
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}