aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2023-04-14 21:09:54 +0200
committerMartin Polden <mpolden@mpolden.no>2023-04-14 21:10:22 +0200
commit90020a7c5741721232c8c62c7ba0028da7771377 (patch)
treebeb7b6bd6970d4b75535c670611c7aa36140155b
parent088b0a8410c7ce7f1fbdb181a39af2b7042f9cdb (diff)
all: remove non-functional /api/v1/HEADmaster
-rw-r--r--README.md138
-rw-r--r--atb/atb.go157
-rw-r--r--atb/atb_test.go147
-rw-r--r--cmd/atb/main.go8
-rw-r--r--http/http.go168
-rw-r--r--http/http_test.go187
-rw-r--r--http/types.go169
-rw-r--r--http/types_test.go173
8 files changed, 13 insertions, 1134 deletions
diff --git a/README.md b/README.md
index 2431afa..cf6751c 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
![Build Status](https://github.com/mpolden/atb/workflows/ci/badge.svg)
A minimal API for bus data in Trondheim, Norway. This API proxies requests to
-AtB/Entur APIs and converts the responses into a sane JSON format.
+Entur APIs and converts the responses into a sane JSON format.
Responses from the proxied APIs are cached. By default bus stops will be cached
for 1 week and departures for 1 minute.
@@ -11,13 +11,11 @@ for 1 week and departures for 1 minute.
As of mid-August 2021 the SOAP-based AtB API no longer returns any departure
data. According to [this blog post on open
data](https://beta.atb.no/blogg/apne-data-og-atb) it appears the preferred API
-is now [Entur](https://developer.entur.org/).
+is now [Entur](https://developer.entur.org/). The `/api/v1/` paths have
+therefore been removed.
-Version 1 of this API will remain implemented for now, but likely won't return
-any usable data.
-
-Version 2 has been implemented and proxies requests to Entur. These are the
-changes in version 2:
+Version 2 has been implemented and proxies requests to Entur instead. These are
+the changes in version 2:
* There is no version 2 variant of `/api/v1/busstops`. Use
https://stoppested.entur.org/ to find valid stop IDs.
@@ -27,7 +25,7 @@ changes in version 2:
* The `registeredDepartureTime` field may be omitted.
* The `isGoingTowardsCentrum` field has moved to the departure object.
-Both version 1 and 2 of this API aims to be compatible with
+This API aims to be compatible with
[BusBuddy](https://github.com/norrs/busbuddy) (which appears to be defunct).
## Usage
@@ -35,8 +33,6 @@ Both version 1 and 2 of this API aims to be compatible with
```
$ atb -h
Usage of atb:
- -c string
- Path to config file (default "config.json")
-d string
Departure cache duration (default "1m")
-l string
@@ -46,15 +42,6 @@ Usage of atb:
-x Allow requests from other domains
```
-## Example config
-
-```
-{
- "Username": "username",
- "Password": "password"
-}
-```
-
## API
### `/`
@@ -67,8 +54,6 @@ Example:
$ curl https://mpolden.no/atb/ | jq .
{
"urls": [
- "https://mpolden.no/atb/v1/busstops",
- "https://mpolden.no/atb/v1/departures",
"https://mpolden.no/atb/v2/departures"
]
}
@@ -105,114 +90,3 @@ $ curl 'https://mpolden.no/atb/v2/departures/41613?direction=inbound' | jq .
]
}
```
-
-
-### `/api/v1/busstops`
-
-Lists all known bus stops.
-
-Example:
-
-```
-$ curl https://mpolden.no/atb/v1/busstops | jq .
-{
- "stops": [
- {
- "stopId": 100633,
- "nodeId": 16011376,
- "description": "Prof. Brochs gt",
- "longitude": 10.398125177823237,
- "latitude": 63.4155348940887,
- "mobileCode": "16011376 (Prof.)",
- "mobileName": "Prof. (16011376)"
- },
- ...
- ]
-}
-```
-
-### `/api/v1/busstops/{node-id}`
-
-Information about the given bus stop, identified by a node ID.
-
-Example:
-
-```
-$ curl https://mpolden.no/atb/v1/busstops/16011376 | jq .
-{
- "stopId": 100633,
- "nodeId": 16011376,
- "description": "Prof. Brochs gt",
- "longitude": 10.398126,
- "latitude": 63.415535,
- "mobileCode": "16011376 (Prof.)",
- "mobileName": "Prof. (16011376)"
-}
-```
-
-As [GeoJSON](http://geojson.org/):
-
-```
-$ curl https://mpolden.no/atb/v1/busstops/16011376?geojson | jq .
-{
- "type": "Feature",
- "geometry": {
- "type": "Point",
- "coordinates": [
- 10.398126,
- 63.415535
- ]
- },
- "properties": {
- "busstop": {
- "stopId": 100633,
- "nodeId": 16011376,
- "description": "Prof. Brochs gt",
- "longitude": 10.398126,
- "latitude": 63.415535,
- "mobileCode": "16011376 (Prof.)",
- "mobileName": "Prof. (16011376)"
- },
- "name": "Prof. Brochs gt"
- }
-}
-```
-
-### `/api/v1/departures`
-
-Lists departure URLs for all known bus stops.
-
-Example:
-
-```
-$ curl -s https://mpolden.no/atb/v1/departures | jq .
-{
- "urls": [
- "https://mpolden.no/atb/v1/departures/15057011",
- ...
- ]
-}
-```
-
-### `/api/v1/departures/{node-id}`
-
-Lists all departures for the given bus stop, identified by a node ID.
-
-Example:
-
-```
-$ curl https://mpolden.no/atb/v1/departures/16011376 | jq .
-{
- "isGoingTowardsCentrum": true,
- "departures": [
- {
- "line": "36",
- "registeredDepartureTime": "2015-02-26T22:55:00.000",
- "scheduledDepartureTime": "2015-02-26T22:54:00.000",
- "destination": "Munkegata M4",
- "isRealtimeData": true
- },
- ...
- ]
-}
-```
diff --git a/atb/atb.go b/atb/atb.go
deleted file mode 100644
index eb629a4..0000000
--- a/atb/atb.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package atb
-
-import (
- "encoding/json"
- "encoding/xml"
- "fmt"
- "io/ioutil"
- "net/http"
- "strings"
-)
-
-// DefaultURL is the default AtB API URL.
-const DefaultURL = "http://st.atb.no/New/InfoTransit/UserServices.asmx"
-
-// Client represents a client which communicates with AtBs API.
-type Client struct {
- Username string
- Password string
- URL string
-}
-
-// BusStops represents a list of bus stops.
-type BusStops struct {
- Stops []BusStop `json:"Fermate"`
-}
-
-// BusStop represents a bus stop.
-type BusStop struct {
- StopID int `json:"cinFermata"`
- NodeID string `json:"codAzNodo"`
- Description string `json:"descrizione"`
- Longitude string `json:"lon"`
- Latitude int `json:"lat"`
- MobileCode string `json:"codeMobile"`
- MobileName string `json:"nomeMobile"`
-}
-
-// Forecasts represents a list of forecasts.
-type Forecasts struct {
- Nodes []NodeInfo `json:"InfoNodo"`
- Forecasts []Forecast `json:"Orari"`
- Total int `json:"total"`
-}
-
-// NodeInfo represents a bus stop, returned as a part of a forecast.
-type NodeInfo struct {
- Name string `json:"nome_Az"`
- NodeID string `json:"codAzNodo"`
- NodeName string `json:"nomeNodo"`
- NodeDescription string `json:"descrNodo"`
- BitMaskProperties string `json:"bitMaskProprieta"`
- MobileCode string `json:"codeMobile"`
- Longitude string `json:"coordLon"`
- Latitude string `json:"coordLat"`
-}
-
-// Forecast represents a single forecast.
-type Forecast struct {
- LineID string `json:"codAzLinea"`
- LineDescription string `json:"descrizioneLinea"`
- RegisteredDepartureTime string `json:"orario"`
- ScheduledDepartureTime string `json:"orarioSched"`
- StationForecast string `json:"statoPrevisione"`
- Destination string `json:"capDest"`
-}
-
-type busStopsRequest struct {
- XMLName xml.Name `xml:"Envelope"`
- Result []byte `xml:"Body>GetBusStopsListResponse>GetBusStopsListResult"`
-}
-
-type forecastRequest struct {
- XMLName xml.Name `xml:"Envelope"`
- Result []byte `xml:"Body>getUserRealTimeForecastByStopResponse>getUserRealTimeForecastByStopResult"`
-}
-
-// NewFromConfig creates a new client where name is the path to the config file.
-func NewFromConfig(name string) (*Client, error) {
- data, err := ioutil.ReadFile(name)
- if err != nil {
- return nil, err
- }
- var client Client
- if err := json.Unmarshal(data, &client); err != nil {
- return nil, err
- }
- if client.URL == "" {
- client.URL = DefaultURL
- }
- return &client, nil
-}
-
-func (c *Client) postXML(body string, dst interface{}) error {
- resp, err := http.Post(c.URL, "application/soap+xml", strings.NewReader(body))
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- dec := xml.NewDecoder(resp.Body)
- if err := dec.Decode(&dst); err != nil {
- return err
- }
- return nil
-}
-
-// BusStops retrieves bus stops from AtBs API.
-func (c *Client) BusStops() (BusStops, error) {
- req := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
-<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
- <soap12:Body>
- <GetBusStopsList xmlns="http://miz.it/infotransit">
- <auth>
- <user>%s</user>
- <password>%s</password>
- </auth>
- </GetBusStopsList>
- </soap12:Body>
-</soap12:Envelope>`, c.Username, c.Password)
-
- var stopsRequest busStopsRequest
- if err := c.postXML(req, &stopsRequest); err != nil {
- return BusStops{}, err
- }
-
- var stops BusStops
- if err := json.Unmarshal(stopsRequest.Result, &stops); err != nil {
- return BusStops{}, err
- }
- return stops, nil
-}
-
-// Forecasts retrieves forecasts from AtBs API, using nodeID to identify the bus stop.
-func (c *Client) Forecasts(nodeID int) (Forecasts, error) {
- req := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
-<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
- <soap12:Body>
- <getUserRealTimeForecastByStop xmlns="http://miz.it/infotransit">
- <auth>
- <user>%s</user>
- <password>%s</password>
- </auth>
- <busStopId>%d</busStopId>
- </getUserRealTimeForecastByStop>
- </soap12:Body>
-</soap12:Envelope>`, c.Username, c.Password, nodeID)
-
- var forecastRequest forecastRequest
- if err := c.postXML(req, &forecastRequest); err != nil {
- return Forecasts{}, err
- }
-
- var forecasts Forecasts
- if err := json.Unmarshal(forecastRequest.Result, &forecasts); err != nil {
- return Forecasts{}, err
- }
- return forecasts, nil
-}
diff --git a/atb/atb_test.go b/atb/atb_test.go
deleted file mode 100644
index a39989f..0000000
--- a/atb/atb_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package atb
-
-import (
- "fmt"
- "net/http"
- "net/http/httptest"
- "reflect"
- "testing"
-)
-
-func newTestServer(path string, body string) *httptest.Server {
- handler := func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8")
- fmt.Fprint(w, body)
- }
- mux := http.NewServeMux()
- mux.HandleFunc(path, handler)
- return httptest.NewServer(mux)
-}
-
-func TestGetBusStops(t *testing.T) {
- server := newTestServer("/", busStopsResponse)
- defer server.Close()
- atb := Client{URL: server.URL}
- expected := BusStops{
- Stops: []BusStop{
- {
- StopID: 100633,
- NodeID: "16011376",
- Description: "Prof. Brochs gt",
- Longitude: "1157514",
- Latitude: 9202874,
- MobileCode: "16011376 (Prof.)",
- MobileName: "Prof. (16011376)",
- },
- },
- }
- stops, err := atb.BusStops()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(stops, expected) {
- t.Fatalf("Expected %+v, got %+v", expected, stops)
- }
-}
-
-func TestGetRealTimeForecast(t *testing.T) {
- server := newTestServer("/", forecastResponse)
- defer server.Close()
- atb := Client{URL: server.URL}
- forecasts, err := atb.Forecasts(16011376)
- expected := Forecasts{
- Total: 1,
- Nodes: []NodeInfo{
- {
- Name: "AtB",
- NodeID: "16011376",
- NodeName: "Prof.",
- NodeDescription: "Prof. Brochs gt",
- BitMaskProperties: "0",
- Longitude: "10.398126",
- Latitude: "63.415535",
- MobileCode: "Prof. Brochs gt",
- },
- },
- Forecasts: []Forecast{
- {
- LineID: "6",
- LineDescription: "6",
- RegisteredDepartureTime: "26.02.2015 18:38",
- ScheduledDepartureTime: "26.02.2015 18:01",
- StationForecast: "Prev",
- Destination: "Munkegata M5",
- },
- },
- }
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(forecasts, expected) {
- t.Fatalf("Expected %+v, got %+v", expected, forecasts)
- }
-}
-
-const busStopsResponse = `<?xml version="1.0" encoding="utf-8"?>
-<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
- <soap12:Body>
- <GetBusStopsListResponse xmlns="http://miz.it/infotransit">
- <GetBusStopsListResult>
-{
- "Fermate": [
- {
- "cinAzienda": 1,
- "nomeAzienda": "AtB",
- "cinFermata": 100633,
- "codAzNodo": "16011376",
- "descrizione": "Prof. Brochs gt",
- "lon": "1157514",
- "lat": 9202874,
- "name": "Prof.",
- "codeMobile": "16011376 (Prof.)",
- "nomeMobile": "Prof. (16011376)"
- }
- ]
-}
- </GetBusStopsListResult>
- </GetBusStopsListResponse>
- </soap12:Body>
-</soap12:Envelope>`
-
-const forecastResponse = `<?xml version="1.0" encoding="utf-8"?>
-<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
- <soap12:Body>
- <getUserRealTimeForecastByStopResponse xmlns="http://miz.it/infotransit">
- <getUserRealTimeForecastByStopResult>
-{
- "total": 1,
- "timeServer": "2015-02-26 18:37",
- "InfoNodo": [
- {
- "nome_Az": "AtB",
- "codAzNodo": "16011376",
- "nomeNodo": "Prof.",
- "descrNodo": "Prof. Brochs gt",
- "bitMaskProprieta": "0",
- "codeMobile": "Prof. Brochs gt",
- "coordLon": "10.398126",
- "coordLat": "63.415535"
- }
- ],
- "Orari": [
- {
- "codAzLinea": "6",
- "descrizioneLinea": "6",
- "orario": "26.02.2015 18:38",
- "orarioSched": "26.02.2015 18:01",
- "statoPrevisione": "Prev",
- "capDest": "Munkegata M5",
- "turnoMacchina": "57",
- "descrizionePercorso": "39"
- }
- ]
-}
- </getUserRealTimeForecastByStopResult>
- </getUserRealTimeForecastByStopResponse>
- </soap12:Body>
-</soap12:Envelope>`
diff --git a/cmd/atb/main.go b/cmd/atb/main.go
index 7b7e131..6335c1f 100644
--- a/cmd/atb/main.go
+++ b/cmd/atb/main.go
@@ -5,7 +5,6 @@ import (
"log"
"time"
- "github.com/mpolden/atb/atb"
"github.com/mpolden/atb/entur"
"github.com/mpolden/atb/http"
)
@@ -25,18 +24,13 @@ func mustParseDuration(s string) time.Duration {
func main() {
listen := flag.String("l", ":8080", "Listen address")
- config := flag.String("c", "config.json", "Path to config file")
stopTTL := flag.String("s", "168h", "Bus stop cache duration")
departureTTL := flag.String("d", "1m", "Departure cache duration")
cors := flag.Bool("x", false, "Allow requests from other domains")
flag.Parse()
- atb, err := atb.NewFromConfig(*config)
- if err != nil {
- log.Fatal(err)
- }
entur := entur.New("")
- server := http.New(atb, entur, mustParseDuration(*stopTTL), mustParseDuration(*departureTTL), *cors)
+ server := http.New(entur, mustParseDuration(*stopTTL), mustParseDuration(*departureTTL), *cors)
log.Printf("Listening on %s", *listen)
if err := server.ListenAndServe(*listen); err != nil {
diff --git a/http/http.go b/http/http.go
index a72f89e..09a3064 100644
--- a/http/http.go
+++ b/http/http.go
@@ -10,7 +10,6 @@ import (
"strconv"
"time"
- "github.com/mpolden/atb/atb"
"github.com/mpolden/atb/cache"
"github.com/mpolden/atb/entur"
)
@@ -22,7 +21,6 @@ const (
// Server represents an Server server.
type Server struct {
- ATB *atb.Client
Entur *entur.Client
CORS bool
cache *cache.Cache
@@ -70,52 +68,6 @@ func filterDepartures(departures []Departure, direction string) []Departure {
return departures
}
-func (s *Server) getBusStops(urlPrefix string) (BusStops, bool, error) {
- const cacheKey = "stops"
- cached, hit := s.cache.Get(cacheKey)
- if hit {
- return cached.(BusStops), hit, nil
- }
- atbBusStops, err := s.ATB.BusStops()
- if err != nil {
- return BusStops{}, hit, err
- }
- busStops, err := convertBusStops(atbBusStops)
- if err != nil {
- return BusStops{}, hit, err
- }
- for i := range busStops.Stops {
- busStops.Stops[i].URL = fmt.Sprintf("%s/api/v1/busstops/%d", urlPrefix, busStops.Stops[i].NodeID)
- }
- // Create a map of nodeIds
- busStops.nodeIDs = make(map[int]*BusStop, len(busStops.Stops))
- for i, s := range busStops.Stops {
- // Store a pointer to the BusStop struct
- busStops.nodeIDs[s.NodeID] = &busStops.Stops[i]
- }
- s.cache.Set(cacheKey, busStops, s.ttl.stops)
- return busStops, hit, nil
-}
-
-func (s *Server) atbDepartures(urlPrefix string, nodeID int) (Departures, bool, error) {
- cacheKey := strconv.Itoa(nodeID)
- cached, hit := s.cache.Get(cacheKey)
- if hit {
- return cached.(Departures), hit, nil
- }
- forecasts, err := s.ATB.Forecasts(nodeID)
- if err != nil {
- return Departures{}, hit, err
- }
- departures, err := convertForecasts(forecasts)
- if err != nil {
- return Departures{}, hit, err
- }
- departures.URL = fmt.Sprintf("%s/api/v1/departures/%d", urlPrefix, nodeID)
- s.cache.Set(cacheKey, departures, s.ttl.departures)
- return departures, hit, nil
-}
-
func (s *Server) enturDepartures(urlPrefix string, stopID int, direction string) (Departures, bool, error) {
cacheKey := strconv.Itoa(stopID)
cached, hit := s.cache.Get(cacheKey)
@@ -143,94 +95,6 @@ func (s *Server) setCacheHeader(w http.ResponseWriter, hit bool) {
w.Header().Set("X-Cache", v)
}
-// BusStopsHandler is a handler for retrieving bus stops.
-func (s *Server) BusStopsHandler(w http.ResponseWriter, r *http.Request) (interface{}, *Error) {
- busStops, hit, err := s.getBusStops(urlPrefix(r))
- if err != nil {
- return nil, &Error{
- err: err,
- Status: http.StatusInternalServerError,
- Message: "Failed to get bus stops from AtB",
- }
- }
- s.setCacheHeader(w, hit)
- _, geojson := r.URL.Query()["geojson"]
- if geojson {
- return busStops.GeoJSON(), nil
- }
- return busStops, nil
-}
-
-// BusStopHandler is a handler for retrieving info about a bus stop.
-func (s *Server) BusStopHandler(w http.ResponseWriter, r *http.Request) (interface{}, *Error) {
- nodeID, err := strconv.Atoi(filepath.Base(r.URL.Path))
- if err != nil {
- return nil, &Error{
- err: err,
- Status: http.StatusBadRequest,
- Message: "Invalid nodeID",
- }
- }
- busStops, hit, err := s.getBusStops(urlPrefix(r))
- if err != nil {
- return nil, &Error{
- err: err,
- Status: http.StatusInternalServerError,
- Message: "Failed to get bus stops from AtB",
- }
- }
- busStop, ok := busStops.nodeIDs[nodeID]
- if !ok {
- return nil, &Error{
- Status: http.StatusNotFound,
- Message: "Unknown bus stop",
- }
- }
- s.setCacheHeader(w, hit)
- _, geojson := r.URL.Query()["geojson"]
- if geojson {
- return busStop.GeoJSON(), nil
- }
- return busStop, nil
-}
-
-// DepartureHandler is a handler for retrieving departures for a given bus stop.
-func (s *Server) DepartureHandler(w http.ResponseWriter, r *http.Request) (interface{}, *Error) {
- nodeID, err := strconv.Atoi(filepath.Base(r.URL.Path))
- if err != nil {
- return nil, &Error{
- err: err,
- Status: http.StatusBadRequest,
- Message: "Invalid nodeID",
- }
- }
- busStops, _, err := s.getBusStops(urlPrefix(r))
- if err != nil {
- return nil, &Error{
- err: err,
- Status: http.StatusInternalServerError,
- Message: "Failed to get bus stops from AtB",
- }
- }
- _, ok := busStops.nodeIDs[nodeID]
- if !ok {
- return nil, &Error{
- Status: http.StatusNotFound,
- Message: "Unknown bus stop",
- }
- }
- departures, hit, err := s.atbDepartures(urlPrefix(r), nodeID)
- if err != nil {
- return nil, &Error{
- err: err,
- Status: http.StatusInternalServerError,
- Message: "Failed to get departures from AtB",
- }
- }
- s.setCacheHeader(w, hit)
- return departures, nil
-}
-
// DepartureHandlerV2 is a handler which retrieves departures for a given bus stop through Entur.
func (s *Server) DepartureHandlerV2(w http.ResponseWriter, r *http.Request) (interface{}, *Error) {
stopID, err := strconv.Atoi(filepath.Base(r.URL.Path))
@@ -254,49 +118,25 @@ func (s *Server) DepartureHandlerV2(w http.ResponseWriter, r *http.Request) (int
return departures, nil
}
-// DeparturesHandler lists all known departures.
-func (s *Server) DeparturesHandler(w http.ResponseWriter, r *http.Request) (interface{}, *Error) {
- busStops, hit, err := s.getBusStops(urlPrefix(r))
- if err != nil {
- return nil, &Error{
- err: err,
- Status: http.StatusInternalServerError,
- Message: "Failed to get bus stops from AtB",
- }
- }
- s.setCacheHeader(w, hit)
- var urls struct {
- URLs []string `json:"urls"`
- }
- urls.URLs = make([]string, len(busStops.Stops))
- for i, stop := range busStops.Stops {
- urls.URLs[i] = fmt.Sprintf("%s/api/v1/departures/%d", urlPrefix(r), stop.NodeID)
- }
- return urls, nil
-}
-
// DefaultHandler lists known URLs.
func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) (interface{}, *Error) {
if r.URL.Path != "/" {
return nil, &Error{Status: http.StatusNotFound, Message: "Resource not found"}
}
prefix := urlPrefix(r)
- busStopsURL := fmt.Sprintf("%s/api/v1/busstops", prefix)
- departuresURL := fmt.Sprintf("%s/api/v1/departures", prefix)
departuresV2URL := fmt.Sprintf("%s/api/v2/departures", prefix)
return struct {
URLs []string `json:"urls"`
}{
- []string{busStopsURL, departuresURL, departuresV2URL},
+ []string{departuresV2URL},
}, nil
}
// New returns a new Server using given clients to communicate with AtB and Entur. stopTTL and departureTTL control the
// cache TTL bus stops and departures.
-func New(atb *atb.Client, entur *entur.Client, stopTTL, departureTTL time.Duration, cors bool) *Server {
+func New(entur *entur.Client, stopTTL, departureTTL time.Duration, cors bool) *Server {
cache := cache.New(time.Minute)
return &Server{
- ATB: atb,
Entur: entur,
CORS: cors,
cache: cache,
@@ -345,10 +185,6 @@ func requestFilter(next http.Handler, cors bool) http.Handler {
// Handler returns a root handler for the API.
func (s *Server) Handler() http.Handler {
mux := http.NewServeMux()
- mux.Handle("/api/v1/busstops", appHandler(s.BusStopsHandler))
- mux.Handle("/api/v1/busstops/", appHandler(s.BusStopHandler))
- mux.Handle("/api/v1/departures", appHandler(s.DeparturesHandler))
- mux.Handle("/api/v1/departures/", appHandler(s.DepartureHandler))
mux.Handle("/api/v2/departures", appHandler(s.DepartureHandlerV2))
mux.Handle("/api/v2/departures/", appHandler(s.DepartureHandlerV2))
mux.Handle("/", appHandler(s.DefaultHandler))
diff --git a/http/http_test.go b/http/http_test.go
index f74db42..b3792b9 100644
--- a/http/http_test.go
+++ b/http/http_test.go
@@ -7,35 +7,16 @@ import (
"log"
"net/http"
"net/http/httptest"
- "strings"
"testing"
"time"
- "github.com/mpolden/atb/atb"
"github.com/mpolden/atb/entur"
)
func apiTestServer() *httptest.Server {
handler := func(w http.ResponseWriter, r *http.Request) {
- isSOAP := r.Header.Get("Content-Type") == "application/soap+xml"
- if isSOAP {
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- panic(err)
- }
- xml := string(b)
- w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8")
- if strings.Contains(xml, "GetBusStopsList") {
- fmt.Fprint(w, busStopsResponse)
- } else if strings.Contains(xml, "getUserRealTimeForecastByStop") {
- fmt.Fprint(w, forecastResponse)
- } else {
- panic("unknown request body: " + xml)
- }
- } else {
- w.Header().Set("Content-Type", "application/json; charset=utf-8")
- fmt.Fprint(w, enturResponse)
- }
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ fmt.Fprint(w, enturResponse)
}
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
@@ -44,9 +25,8 @@ func apiTestServer() *httptest.Server {
func testServers() (*httptest.Server, *Server) {
apiServer := apiTestServer()
- atb := &atb.Client{URL: apiServer.URL}
entur := &entur.Client{URL: apiServer.URL}
- server := New(atb, entur, 168*time.Hour, 1*time.Minute, false)
+ server := New(entur, 168*time.Hour, 1*time.Minute, false)
return apiServer, server
}
@@ -78,21 +58,7 @@ func TestAPI(t *testing.T) {
// Unknown resources
{"/not-found", `{"status":404,"message":"Resource not found"}`, 404},
// List know URLs
- {"/", fmt.Sprintf(`{"urls":["%s/api/v1/busstops","%s/api/v1/departures","%s/api/v2/departures"]}`, httpSrv.URL, httpSrv.URL, httpSrv.URL), 200},
- // List all bus stops
- {"/api/v1/busstops", fmt.Sprintf(`{"stops":[{"url":"%s/api/v1/busstops/16011376","stopId":100633,"nodeId":16011376,"description":"Prof. Brochs gt","longitude":10.398126,"latitude":63.415535,"mobileCode":"16011376 (Prof.)","mobileName":"Prof. (16011376)"}]}`, httpSrv.URL), 200},
- // List all departures
- {"/api/v1/departures", fmt.Sprintf(`{"urls":["%s/api/v1/departures/16011376"]}`, httpSrv.URL), 200},
- // Show specific bus stop
- {"/api/v1/busstops/", `{"status":400,"message":"Invalid nodeID"}`, 400},
- {"/api/v1/busstops/foo", `{"status":400,"message":"Invalid nodeID"}`, 400},
- {"/api/v1/busstops/42", `{"status":404,"message":"Unknown bus stop"}`, 404},
- {"/api/v1/busstops/16011376", fmt.Sprintf(`{"url":"%s/api/v1/busstops/16011376","stopId":100633,"nodeId":16011376,"description":"Prof. Brochs gt","longitude":10.398126,"latitude":63.415535,"mobileCode":"16011376 (Prof.)","mobileName":"Prof. (16011376)"}`, httpSrv.URL), 200},
- // Show specific departure
- {"/api/v1/departures/", `{"status":400,"message":"Invalid nodeID"}`, 400},
- {"/api/v1/departures/foo", `{"status":400,"message":"Invalid nodeID"}`, 400},
- {"/api/v1/departures/42", `{"status":404,"message":"Unknown bus stop"}`, 404},
- {"/api/v1/departures/16011376", fmt.Sprintf(`{"url":"%s/api/v1/departures/16011376","isGoingTowardsCentrum":true,"departures":[{"line":"6","registeredDepartureTime":"2015-02-26T18:38:00.000","scheduledDepartureTime":"2015-02-26T18:01:00.000","destination":"Munkegata M5","isRealtimeData":true}]}`, httpSrv.URL), 200},
+ {"/", fmt.Sprintf(`{"urls":["%s/api/v2/departures"]}`, httpSrv.URL), 200},
// Show specific departure (v2)
{"/api/v2/departures", `{"status":400,"message":"Invalid stop ID. Use https://stoppested.entur.org/ to find stop IDs."}`, 400},
{"/api/v2/departures/", `{"status":400,"message":"Invalid stop ID. Use https://stoppested.entur.org/ to find stop IDs."}`, 400},
@@ -135,151 +101,6 @@ func TestURLPrefix(t *testing.T) {
}
}
-func TestGetBusStops(t *testing.T) {
- apiServer, server := testServers()
- defer apiServer.Close()
- _, _, err := server.getBusStops("")
- if err != nil {
- t.Fatal(err)
- }
- cached, ok := server.cache.Get("stops")
- if !ok {
- t.Fatal("Expected true")
- }
- busStops, ok := cached.(BusStops)
- if !ok {
- t.Fatal("Expected true")
- }
- if len(busStops.Stops) != 1 {
- t.Fatal("Expected length to be 1")
- }
- if len(busStops.nodeIDs) != 1 {
- t.Fatal("Expected length to be 1")
- }
-}
-
-func TestGetBusStopsCache(t *testing.T) {
- apiServer, server := testServers()
- defer apiServer.Close()
- _, hit, err := server.getBusStops("")
- if err != nil {
- t.Fatal(err)
- }
- if hit {
- t.Error("Expected false")
- }
- _, hit, err = server.getBusStops("")
- if err != nil {
- t.Fatal(err)
- }
- if !hit {
- t.Error("Expected true")
- }
-}
-
-func TestGetDepartures(t *testing.T) {
- apiServer, server := testServers()
- defer apiServer.Close()
- _, _, err := server.atbDepartures("", 16011376)
- if err != nil {
- t.Fatal(err)
- }
- cached, ok := server.cache.Get("16011376")
- if !ok {
- t.Fatal("Expected true")
- }
- departures, ok := cached.(Departures)
- if !ok {
- t.Fatal("Expected true")
- }
- if len(departures.Departures) != 1 {
- t.Fatal("Expected length to be 1")
- }
-}
-
-func TestGetDeparturesCache(t *testing.T) {
- apiServer, server := testServers()
- defer apiServer.Close()
- _, hit, err := server.atbDepartures("", 16011376)
- if err != nil {
- t.Fatal(err)
- }
- if hit {
- t.Error("Expected false")
- }
- _, hit, err = server.atbDepartures("", 16011376)
- if err != nil {
- t.Fatal(err)
- }
- if !hit {
- t.Error("Expected true")
- }
-}
-
-const busStopsResponse = `<?xml version="1.0" encoding="utf-8"?>
-<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
- <soap12:Body>
- <GetBusStopsListResponse xmlns="http://miz.it/infotransit">
- <GetBusStopsListResult>
-{
- "Fermate": [
- {
- "cinAzienda": 1,
- "nomeAzienda": "AtB",
- "cinFermata": 100633,
- "codAzNodo": "16011376",
- "descrizione": "Prof. Brochs gt",
- "lon": "1157514",
- "lat": 9202874,
- "name": "Prof.",
- "codeMobile": "16011376 (Prof.)",
- "nomeMobile": "Prof. (16011376)"
- }
- ]
-}
- </GetBusStopsListResult>
- </GetBusStopsListResponse>
- </soap12:Body>
-</soap12:Envelope>`
-
-const forecastResponse = `<?xml version="1.0" encoding="utf-8"?>
-<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
- <soap12:Body>
- <getUserRealTimeForecastByStopResponse xmlns="http://miz.it/infotransit">
- <getUserRealTimeForecastByStopResult>
-{
- "total": 1,
- "timeServer": "2015-02-26 18:37",
- "InfoNodo": [
- {
- "nome_Az": "AtB",
- "codAzNodo": "16011376",
- "nomeNodo": "Prof.",
- "descrNodo": "Prof. Brochs gt",
- "bitMaskProprieta": "0",
- "codeMobile": "Prof. Brochs gt",
- "coordLon": "10.398126",
- "coordLat": "63.415535"
- }
- ],
- "Orari": [
- {
- "codAzLinea": "6",
- "descrizioneLinea": "6",
- "orario": "26.02.2015 18:38",
- "orarioSched": "26.02.2015 18:01",
- "statoPrevisione": "Prev",
- "capDest": "Munkegata M5",
- "turnoMacchina": "57",
- "descrizionePercorso": "39"
- }
- ]
-}
- </getUserRealTimeForecastByStopResult>
- </getUserRealTimeForecastByStopResponse>
- </soap12:Body>
-</soap12:Envelope>`
-
const enturResponse = `{
"data": {
"stopPlace": {
diff --git a/http/types.go b/http/types.go
index 2ed25e3..de6d652 100644
--- a/http/types.go
+++ b/http/types.go
@@ -1,12 +1,6 @@
package http
import (
- "math"
- "strconv"
- "strings"
- "time"
-
- "github.com/mpolden/atb/atb"
"github.com/mpolden/atb/entur"
)
@@ -28,25 +22,6 @@ type BusStop struct {
MobileName string `json:"mobileName"`
}
-// GeoJSON represents the top-level object of the GeoJSON format.
-type GeoJSON struct {
- Type string `json:"type"`
- Geometry `json:"geometry"`
- Properties map[string]interface{} `json:"properties"`
-}
-
-// Geometry represents the geometry object of the GeoJSON format.
-type Geometry struct {
- Type string `json:"type"`
- Coordinates []float64 `json:"coordinates"`
-}
-
-// GeoJSONCollection represents a collection of GeoJSON feature objects.
-type GeoJSONCollection struct {
- Type string `json:"type"`
- Features []GeoJSON `json:"features"`
-}
-
// Departures represents a list of departures, from a given bus stop.
type Departures struct {
URL string `json:"url"`
@@ -71,120 +46,6 @@ type Error struct {
Message string `json:"message"`
}
-func convertBusStop(s atb.BusStop) (BusStop, error) {
- nodeID, err := strconv.Atoi(s.NodeID)
- if err != nil {
- return BusStop{}, err
- }
- longitude, err := strconv.Atoi(s.Longitude)
- if err != nil {
- return BusStop{}, err
- }
- lat, lon := ConvertCoordinates(s.Latitude, longitude)
- return BusStop{
- StopID: s.StopID,
- NodeID: nodeID,
- Description: s.Description,
- Longitude: ceilN(lon, 6),
- Latitude: ceilN(lat, 6),
- MobileCode: s.MobileCode,
- MobileName: s.MobileName,
- }, nil
-}
-
-func convertBusStops(s atb.BusStops) (BusStops, error) {
- stops := make([]BusStop, 0, len(s.Stops))
- for _, stop := range s.Stops {
- converted, err := convertBusStop(stop)
- if err != nil {
- return BusStops{}, err
- }
- stops = append(stops, converted)
- }
- return BusStops{Stops: stops}, nil
-}
-
-// ConvertTime converts time from AtBs format to ISO 8601.
-func ConvertTime(src string) (string, error) {
- t, err := time.Parse("02.01.2006 15:04", src)
- if err != nil {
- return "", err
- }
- return t.Format("2006-01-02T15:04:05.000"), nil
-}
-
-// IsRealtime returns a boolean indicating whether stationForecast is realtime.
-func IsRealtime(stationForecast string) bool {
- return strings.EqualFold(stationForecast, "prev")
-}
-
-func convertForecast(f atb.Forecast) (Departure, error) {
- registeredDeparture, err := ConvertTime(f.RegisteredDepartureTime)
- if err != nil {
- return Departure{}, err
- }
- scheduledDeparture, err := ConvertTime(f.ScheduledDepartureTime)
- if err != nil {
- return Departure{}, err
- }
- return Departure{
- LineID: f.LineID,
- Destination: f.Destination,
- RegisteredDepartureTime: registeredDeparture,
- ScheduledDepartureTime: scheduledDeparture,
- IsRealtimeData: IsRealtime(f.StationForecast),
- }, nil
-}
-
-// IsTowardsCentrum returns a boolean indicating whether a bus stop, identified
-// by nodeID, is going to the centrum.
-func IsTowardsCentrum(nodeID int) bool {
- return (nodeID/1000)%2 == 1
-}
-
-// ConvertCoordinates converts latitude and longitude from EPSG:3785 to
-// EPSG:4326.
-func ConvertCoordinates(latitude, longitude int) (float64, float64) {
- const earthRadius = 6378137
-
- originShift := (2 * math.Pi * earthRadius) / 2
-
- lat := (float64(latitude) / originShift) * 180
- lon := (float64(longitude) / originShift) * 180
-
- lat = 180 / math.Pi * (2*math.Atan(
- math.Exp(lat*math.Pi/180)) - math.Pi/2)
- return lat, lon
-}
-
-func ceilN(f float64, n int) float64 {
- shift := math.Pow(10, float64(n))
- return math.Ceil(f*shift) / shift
-}
-
-func convertForecasts(f atb.Forecasts) (Departures, error) {
- towardsCentrum := false
- if len(f.Nodes) > 0 {
- nodeID, err := strconv.Atoi(f.Nodes[0].NodeID)
- if err != nil {
- return Departures{}, err
- }
- towardsCentrum = IsTowardsCentrum(nodeID)
- }
- departures := make([]Departure, 0, len(f.Forecasts))
- for _, forecast := range f.Forecasts {
- departure, err := convertForecast(forecast)
- if err != nil {
- return Departures{}, err
- }
- departures = append(departures, departure)
- }
- return Departures{
- TowardsCentrum: &towardsCentrum,
- Departures: departures,
- }, nil
-}
-
func convertDepartures(enturDepartures []entur.Departure) Departures {
departures := make([]Departure, 0, len(enturDepartures))
const timeLayout = "2006-01-02T15:04:05.000"
@@ -209,33 +70,3 @@ func convertDepartures(enturDepartures []entur.Departure) Departures {
Departures: departures,
}
}
-
-// GeoJSON converts BusStop into the GeoJSON format.
-func (s *BusStop) GeoJSON() GeoJSON {
- geometry := Geometry{
- Type: "Point",
- Coordinates: []float64{s.Longitude, s.Latitude},
- }
- properties := map[string]interface{}{
- "name": s.Description,
- "busstop": s,
- }
- return GeoJSON{
- Type: "Feature",
- Geometry: geometry,
- Properties: properties,
- }
-}
-
-// GeoJSON converts BusStops into a GeoJSON feature collection.
-func (s *BusStops) GeoJSON() GeoJSONCollection {
- features := make([]GeoJSON, 0, len(s.Stops))
- for i := range s.Stops {
- geoJSON := s.Stops[i].GeoJSON()
- features = append(features, geoJSON)
- }
- return GeoJSONCollection{
- Type: "FeatureCollection",
- Features: features,
- }
-}
diff --git a/http/types_test.go b/http/types_test.go
deleted file mode 100644
index 9550001..0000000
--- a/http/types_test.go
+++ /dev/null
@@ -1,173 +0,0 @@
-package http
-
-import (
- "reflect"
- "testing"
-
- "github.com/mpolden/atb/atb"
-)
-
-func TestConvertBusStop(t *testing.T) {
- stop := atb.BusStop{
- StopID: 100633,
- NodeID: "16011376",
- Description: "Prof. Brochs gt",
- Longitude: "1157514",
- Latitude: 9202874,
- MobileCode: "16011376 (Prof.)",
- MobileName: "Prof. (16011376)",
- }
- expected := BusStop{
- StopID: 100633,
- NodeID: 16011376,
- Description: "Prof. Brochs gt",
- Longitude: 10.398126,
- Latitude: 63.415535,
- MobileCode: "16011376 (Prof.)",
- MobileName: "Prof. (16011376)",
- }
- actual, err := convertBusStop(stop)
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("Expected %+v, got %+v", expected, actual)
- }
-}
-
-func TestConvertBusStops(t *testing.T) {
- stops := atb.BusStops{
- Stops: []atb.BusStop{{
- NodeID: "16011376",
- Longitude: "1157514",
- Latitude: 9202874,
- }}}
- expected := BusStops{
- Stops: []BusStop{{
- NodeID: 16011376,
- Longitude: 10.398126,
- Latitude: 63.415535,
- }}}
- actual, err := convertBusStops(stops)
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("Expected %+v, got %+v", expected, actual)
- }
-}
-
-func TestConvertTime(t *testing.T) {
- time, err := ConvertTime("26.02.2015 18:38")
- if err != nil {
- t.Fatal(err)
- }
- expected := "2015-02-26T18:38:00.000"
- if time != expected {
- t.Fatalf("Expected %s, got %s", expected, time)
- }
-}
-
-func TestIsRealtime(t *testing.T) {
- if !IsRealtime("prev") {
- t.Fatal("Expected true")
- }
- if !IsRealtime("Prev") {
- t.Fatal("Expected true")
- }
- if IsRealtime("foo") {
- t.Fatal("Expected false")
- }
-}
-
-func TestConvertForecast(t *testing.T) {
- forecast := atb.Forecast{
- LineID: "6",
- LineDescription: "6",
- RegisteredDepartureTime: "26.02.2015 18:38",
- ScheduledDepartureTime: "26.02.2015 18:01",
- StationForecast: "Prev",
- Destination: "Munkegata M5",
- }
- expected := Departure{
- LineID: "6",
- Destination: "Munkegata M5",
- RegisteredDepartureTime: "2015-02-26T18:38:00.000",
- ScheduledDepartureTime: "2015-02-26T18:01:00.000",
- IsRealtimeData: true,
- }
- actual, err := convertForecast(forecast)
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("Expected %+v, got %+v", expected, actual)
- }
-}
-
-func TestIsTowardsCentrum(t *testing.T) {
- if !IsTowardsCentrum(16011376) {
- t.Fatal("Expected true")
- }
- if IsTowardsCentrum(16010376) {
- t.Fatal("Expected false")
- }
-}
-
-func TestConvertForecasts(t *testing.T) {
- forecasts := atb.Forecasts{
- Nodes: []atb.NodeInfo{{NodeID: "16011376"}},
- Forecasts: []atb.Forecast{{
- RegisteredDepartureTime: "26.02.2015 18:38",
- ScheduledDepartureTime: "26.02.2015 18:01",
- }}}
- b := true
- expected := Departures{TowardsCentrum: &b,
- Departures: []Departure{{
- RegisteredDepartureTime: "2015-02-26T18:38:00.000",
- ScheduledDepartureTime: "2015-02-26T18:01:00.000",
- IsRealtimeData: false,
- }}}
- actual, err := convertForecasts(forecasts)
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("Expected %+v, got %+v", expected, actual)
- }
-}
-
-func TestConvertCoordinates(t *testing.T) {
- // Prof. Brochs gate
- latitude, longitude := 9202565, 1157522
- lat, lon := ConvertCoordinates(latitude, longitude)
- if expected := 63.41429265308724; lat != expected {
- t.Fatalf("Expected %f, got %f", expected, lat)
- }
- if expected := 10.398197043045966; lon != expected {
- t.Fatalf("Expected %f, got %f", expected, lon)
- }
-
- // Ilsvika
- latitude, longitude = 9206756, 1152920
- lat, lon = ConvertCoordinates(latitude, longitude)
- if expected := 63.43113671582598; lat != expected {
- t.Fatalf("Expected %f, got %f", expected, lat)
- }
- if expected := 10.356856573670786; lon != expected {
- t.Fatalf("Expected %f, got %f", expected, lon)
- }
-}
-
-func TestCeilN(t *testing.T) {
- expected := 1.234567
- actual := ceilN(1.2345661, 6)
- if actual != expected {
- t.Fatalf("Expected %f, got %f", expected, actual)
- }
- expected = 1.234567
- actual = ceilN(1.2345665, 6)
- if actual != expected {
- t.Fatalf("Expected %f, got %f", expected, actual)
- }
-}