trains

Notifications for the new MBTA trains. Probably no longer functional.

git clone https://code.pdelong.com/trains.git

  1package main
  2
  3// This file contains the routines and information necessary to talk to the MBTA API
  4
  5import (
  6	"encoding/json"
  7	"fmt"
  8	"io/ioutil"
  9	"log"
 10	"net/http"
 11	"strconv"
 12	"strings"
 13)
 14
 15type apiResponse struct {
 16	Data []struct {
 17		Attributes struct {
 18			DirectionID int    `json:"direction_id"`
 19			Label       string `json:"label"`
 20		} `json:"attributes"`
 21		Relationships struct {
 22			Route struct {
 23				Data struct {
 24					ID string `json:"id"`
 25				} `json:"data"`
 26			} `json:"route"`
 27			Stop struct {
 28				Data struct {
 29					ID string `json:"id"`
 30				} `json:"data"`
 31			} `json:"stop"`
 32			Trip struct {
 33				Data struct {
 34					ID string `json:"id"`
 35				} `json:"data"`
 36			} `json:"trip"`
 37		} `json:"relationships"`
 38	} `json:"data"`
 39	Included []struct {
 40		Attributes struct {
 41			Name string `json:"name"`
 42		} `json:"attributes"`
 43		ID string `json:"id"`
 44	} `json:"included"`
 45}
 46
 47// Calls into the MBTA API to retrieve a list of all trains. and returns a list
 48// of all of them, or an error if there was one.
 49//
 50// No filtering is performed. That is the job of the caller.
 51func GetTrains() ([]Train, error) {
 52	resp, err := http.Get(apiEndpoint)
 53	if err != nil {
 54		return nil, fmt.Errorf("unable to query MBTA API: %v", err)
 55	}
 56	defer resp.Body.Close()
 57
 58	if resp.StatusCode != http.StatusOK {
 59		errorStr := fmt.Sprintf("Received non-200 result from API: %v", resp.StatusCode)
 60		log.Println(errorStr)
 61		return nil, fmt.Errorf(errorStr)
 62	}
 63
 64	body, err := ioutil.ReadAll(resp.Body)
 65	if err != nil {
 66		return nil, fmt.Errorf("unable to read API response: %v", err)
 67	}
 68
 69	var decoded apiResponse
 70	if err := json.Unmarshal(body, &decoded); err != nil {
 71		return nil, fmt.Errorf("unable to unmarshal JSON response: %v", err)
 72	}
 73
 74	stopNames := make(map[string]string)
 75	for _, stop := range decoded.Included {
 76		stopNames[stop.ID] = stop.Attributes.Name
 77	}
 78
 79	trains := make([]Train, 0)
 80	for _, train := range decoded.Data {
 81		labelStrs := strings.Split(train.Attributes.Label, "-")
 82		labels := make([]int, 0)
 83		for _, label := range labelStrs {
 84			v, err := strconv.Atoi(label)
 85			if err != nil {
 86				// It seems like sometimes we end up with empty
 87				// cars. There's probably a trailing dash.
 88				if label == "" {
 89					log.Println("Found strange cars list: ", train.Attributes.Label)
 90					continue
 91				}
 92				panic(err)
 93			}
 94			labels = append(labels, v)
 95		}
 96
 97		line := StringToLine(train.Relationships.Route.Data.ID)
 98
 99		if line == LineUnknown {
100			panic("unknown line: " + train.Relationships.Route.Data.ID)
101		}
102
103		trains = append(trains, Train{
104			Line:      line,
105			Cars:      labels,
106			Stop:      stopNames[train.Relationships.Stop.Data.ID],
107			Direction: IntToDirection(train.Attributes.DirectionID, line),
108			Trip:      train.Relationships.Trip.Data.ID,
109		})
110	}
111
112	return trains, nil
113}