restbus
Version:
RESTful JSON API for the NextBus Inc. public XML feed.
182 lines (162 loc) • 5.71 kB
JavaScript
var https = require('https');
var utils = require('../utils');
var C = utils.c;
var NBXML_FEED = C.NEXTBUS_XMLFEED;
var last_time = 0;
var vehicles = {};
function getJson(vehicle) {
var v = {},
$ = vehicle.$;
v.id = $.id;
v.routeId = !$.routeTag ? null : $.routeTag;
v.directionId = !$.dirTag ? null : $.dirTag;
v.predictable = !!($.predictable === 'true');
v.secsSinceReport = parseInt($.secsSinceReport, 10);
v.kph = !$.speedKmHr ? null : parseInt($.speedKmHr, 10);
v.heading = parseInt($.heading, 10);
v.lat = parseFloat($.lat);
v.lon = parseFloat($.lon);
v.leadingVehicleId = !$.leadingVehicleId ? null : $.leadingVehicleId;
return v;
}
function getLinks(hrefs, v, req) {
var params = req.params,
r = v.routeId || params.route, // To protect against XML feed inconsistencies when vehicles log off route
a = params.agency,
s = {
href: hrefs.self,
type: C.MTJSON,
rel: 'self',
rt: C.VEH,
title: ['Transit agency ', a, ' vehicle ', v.id, '.'].join('')
},
rsection = {
href: hrefs.route,
type: C.MTJSON,
rel: 'section',
rt: C.VEH,
title: [
'A collection of vehicles currently on route ',
r,
' for agency ',
a,
'.'
].join('')
},
asection = {
href: hrefs.agency,
type: C.MTJSON,
rel: 'section',
rt: C.VEH,
title: ['A collection of vehicles for agency ', a, '.'].join('')
},
avia = {
href: hrefs.agency.replace('/vehicles', ''),
type: C.MTJSON,
rel: 'via',
rt: C.AGY,
title: ['Transit agency ', a, ' details.'].join('')
},
to = [],
from = [];
if (params.vehicle) {
// Canonical vehicle resource (self) requested (/agencies/:a/vehicles/:v)
from = from.concat([asection, rsection]);
} else if (params.route) {
// /agencies/:a/routes/:r/vehicles
from = from.concat([asection, rsection]);
} else {
to.push(s);
from = from.concat([asection, rsection, avia]);
}
return { self: s, to: to, from: from };
}
/**
* Method for retrieving ALL vehicles for an agency. Interestingly, if you inclue the 'r=' query
* parameter in the request to NextBus, but leave it without a value, then all agency vehicles are
* returned as desired. However, including it allows us to reuse this method for vehicles.routelist.
*
* @param {Object:req} The node native https.ClientRequest object.
* @param {Object:res} The node native https.ServerResponse object.
*/
vehicles.list = function (req, res) {
var p = req.params,
a = p.agency,
r = p.route ? p.route : '',
path = [NBXML_FEED, '?command=vehicleLocations&a=', a, '&r=', r, '&t=0'].join('');
https
.get(utils.getOptionsWithPath(path), function (nbres) {
utils.getJsFromXml(nbres, function (err, js) {
var json = [],
nberr,
vehicles,
uri,
ruri;
if (!err) {
nberr = js.body.Error && js.body.Error[0];
uri = utils.getOpenPath(req);
// Adjust uri for building links if list() is invoked from routelist()
if (r) uri = uri.replace(/routes\/.*\/vehicles/gi, 'vehicles');
ruri = uri.replace('/vehicles', '/routes'); // will mess up vehicles.routelist()
if (!nberr) {
/**
* NextBus returns only a <lastTime> element when a bogus route id is used in the request.
* They DO NOT return an <Error> element. For now I will immitate their response and
* return an empty array of vehicles instead of an HTTP 404 response.
*/
vehicles = js.body.vehicle;
if (vehicles)
vehicles.forEach(function (vehicle) {
var v = getJson(vehicle),
href,
rhref;
href = [uri, '/', v.id].join('');
rhref = [ruri, '/', v.routeId, '/vehicles'].join('');
if (req.query.links !== 'false') {
v._links = getLinks({ self: href, agency: uri, route: rhref }, v, req);
}
json.push(v);
});
last_time = parseInt(js.body.lastTime[0].$.time, 10);
res.status(200).json(json);
} else utils.nbXmlError(nberr, res);
} else utils.streamOrParseError(err, js, res);
});
})
.on('error', function (e) {
utils.nbRequestError(e, res);
});
};
vehicles.routelist = function (req, res) {
vehicles.list(req, res);
};
vehicles.get = function (req, res) {
var p = req.params,
a = p.agency,
v = p.vehicle,
path = [NBXML_FEED, '?command=vehicleLocation&a=', a, '&v=', v].join('');
https
.get(utils.getOptionsWithPath(path), function (nbres) {
utils.getJsFromXml(nbres, function (err, js) {
var nberr, uri, auri, ruri;
if (!err) {
nberr = js.body.Error && js.body.Error[0];
if (!nberr) {
var v = getJson(js.body.vehicle[0]), // NextBus doesn't use <lastTime> element for this command
uri = utils.getOpenPath(req),
auri = uri.replace('/' + v.id, ''),
rstr = ['/routes/', v.routeId, '/vehicles'].join(''),
ruri = auri.replace('/vehicles', rstr);
if (req.query.links !== 'false') {
v._links = getLinks({ self: uri, agency: auri, route: ruri }, v, req);
}
res.status(200).json(v);
} else utils.nbXmlError(nberr, res);
} else utils.streamOrParseError(err, js, res);
});
})
.on('error', function (e) {
utils.nbRequestError(e, res);
});
};
module.exports = vehicles;