query live movements in an area

This commit is contained in:
Jannis R 2017-11-20 17:37:08 +01:00
parent 33417a6e61
commit 3fb0943680
No known key found for this signature in database
GPG key ID: 0FE83946296A88A5
7 changed files with 110 additions and 31 deletions

View file

@ -8,5 +8,6 @@ module.exports = {
address: require('./address'),
poi: require('./poi'),
location: require('./location'),
locationFilter: require('./location-filter')
locationFilter: require('./location-filter'),
rectangle: require('./rectangle')
}

16
format/rectangle.js Normal file
View file

@ -0,0 +1,16 @@
'use strict'
const formatRectangle = (profile, north, west, south, east) => {
return {
llCrd: {
x: profile.formatCoord(west),
y: profile.formatCoord(south)
},
urCrd: {
x: profile.formatCoord(east),
y: profile.formatCoord(north)
}
}
}
module.exports = formatRectangle

View file

@ -184,7 +184,48 @@ const createClient = (profile) => {
})
}
return {departures, journeys, locations, nearby, journeyPart}
const radar = (north, west, south, east, opt) => {
if ('number' !== typeof north) throw new Error('north must be a number.')
if ('number' !== typeof west) throw new Error('west must be a number.')
if ('number' !== typeof south) throw new Error('south must be a number.')
if ('number' !== typeof east) throw new Error('east must be a number.')
opt = Object.assign({
results: 256, // maximum number of vehicles
duration: 30, // compute frames for the next n seconds
frames: 3 // nr of frames to compute
}, opt || {})
opt.when = opt.when || new Date()
const durationPerStep = opt.duration / Math.max(opt.frames, 1) * 1000
return request(profile, {
meth: 'JourneyGeoPos',
req: {
maxJny: opt.results,
onlyRT: false, // todo: does this mean "only realtime"?
date: profile.formatDate(profile, opt.when),
time: profile.formatTime(profile, opt.when),
// todo: would a ring work here as well?
rect: profile.formatRectangle(profile, north, west, south, east),
perSize: opt.duration * 1000,
perStep: Math.round(durationPerStep),
ageOfReport: true, // todo: what is this?
jnyFltrL: [
// todo: use `profile.formatProducts(opt.products || {})`
{type: 'PROD', mode: 'INC', value: '127'}
],
trainPosMode: 'CALC' // todo: what is this? what about realtime?
}
})
.then((d) => {
if (!Array.isArray(d.jnyL)) return []
const parse = profile.parseMovement(profile, d.locations, d.lines, d.remarks)
return d.jnyL.map(parse)
})
}
return {departures, journeys, locations, nearby, journeyPart, radar}
}
module.exports = createClient

View file

@ -21,6 +21,7 @@ const formatPoi = require('../format/poi')
const formatStation = require('../format/station')
const formatTime = require('../format/time')
const formatLocation = require('../format/location')
const formatRectangle = require('../format/rectangle')
const id = x => x
@ -52,7 +53,8 @@ const defaultProfile = {
formatPoi,
formatStation,
formatTime,
formatLocation
formatLocation,
formatRectangle
}
module.exports = defaultProfile

View file

@ -27,7 +27,7 @@ const createParseMovement = (profile, locations, lines, remarks) => {
}
const res = {
direction: m.dirTxt,
direction: profile.parseStationName(m.dirTxt),
line: lines[m.prodX] || null,
coordinates: m.pos ? {
latitude: m.pos.y / 1000000,

View file

@ -3,31 +3,52 @@
const isRoughlyEqual = require('is-roughly-equal')
const floor = require('floordate')
const assertValidStation = (t, s) => {
const assertValidStation = (t, s, coordsOptional = false) => {
t.equal(typeof s.type, 'string')
t.equal(s.type, 'station')
t.equal(typeof s.id, 'string')
t.equal(typeof s.name, 'string')
if (!coordsOptional) {
if (!s.coordinates) console.trace()
t.ok(s.coordinates)
}
if (s.coordinates) {
t.equal(typeof s.coordinates.latitude, 'number')
t.equal(typeof s.coordinates.longitude, 'number')
}
}
const assertValidPoi = (t, p) => {
t.equal(typeof p.type, 'string')
t.equal(p.type, 'poi')
t.equal(typeof p.id, 'string')
t.equal(typeof p.name, 'string')
t.ok(p.coordinates)
if (s.coordinates) {
t.equal(typeof p.coordinates.latitude, 'number')
t.equal(typeof p.coordinates.longitude, 'number')
}
}
const assertValidAddress = (t, a) => {
t.equal(typeof a.type, 'string')
t.equal(a.type, 'address')
t.equal(typeof a.name, 'string')
t.ok(a.coordinates)
if (s.coordinates) {
t.equal(typeof a.coordinates.latitude, 'number')
t.equal(typeof a.coordinates.longitude, 'number')
}
}
const assertValidLocation = (t, l) => {
t.equal(typeof l.type, 'string')
if (l.type === 'station') assertValidStation(t, l)
else if (l.type === 'poi') assertValidPoi(t, l)
else if (l.type === 'address') assertValidAddress(t, l)
else t.fail('invalid type ' + l.type)
t.equal(typeof l.name, 'string')
t.ok(l.coordinates)
t.equal(typeof l.coordinates.latitude, 'number')
t.equal(typeof l.coordinates.longitude, 'number')
}
const isValidMode = (m) => {
@ -40,7 +61,6 @@ const isValidMode = (m) => {
const assertValidLine = (t, l) => {
t.equal(l.type, 'line')
t.equal(typeof l.name, 'string')
if (!isValidMode(l.mode)) console.error(l)
t.ok(isValidMode(l.mode), 'invalid mode ' + l.mode)
t.equal(typeof l.product, 'string')
t.equal(l.public, true)
@ -50,14 +70,14 @@ const isValidDateTime = (w) => {
return !Number.isNaN(+new Date(w))
}
const assertValidStopover = (t, s) => {
const assertValidStopover = (t, s, coordsOptional = false) => {
if ('arrival' in s) t.ok(isValidDateTime(s.arrival))
if ('departure' in s) t.ok(isValidDateTime(s.departure))
if (!('arrival' in s) && !('departure' in s)) {
t.fail('stopover doesn\'t contain arrival or departure')
}
t.ok(s.station)
assertValidStation(t, s.station)
assertValidStation(t, s.station, coordsOptional)
}
const minute = 60 * 1000
@ -86,5 +106,5 @@ module.exports = {
assertValidLine,
isValidDateTime,
assertValidStopover,
when, isValidWhen, assertValidWhen
hour, when, isValidWhen, assertValidWhen
}

View file

@ -269,8 +269,7 @@ test('locations', async (t) => {
// todo
test.skip('radar', async (t) => {
test('radar', async (t) => {
const vehicles = await client.radar(52.52411, 13.41002, 52.51942, 13.41709, {
duration: 5 * 60, when
})
@ -290,21 +289,19 @@ test.skip('radar', async (t) => {
t.ok(v.coordinates.longitude <= 15, 'vehicle is too far away')
t.ok(Array.isArray(v.nextStops))
for (let s of v.nextStops) {
assertValidFrameStation(t, s.station)
if (!s.arrival && !s.departure)
t.ifError(new Error('neither arrival nor departure return'))
if (s.arrival) {
t.equal(typeof s.arrival, 'string')
const arr = +new Date(s.arrival)
t.ok(!Number.isNaN(arr))
for (let st of v.nextStops) {
assertValidStopover(t, st, true)
t.strictEqual(st.station.name.indexOf('(Berlin)'), -1)
if (st.arrival) {
t.equal(typeof st.arrival, 'string')
const arr = +new Date(st.arrival)
// note that this can be an ICE train
t.ok(isRoughlyEqual(14 * hour, +when, arr))
}
if (s.departure) {
t.equal(typeof s.departure, 'string')
const dep = +new Date(s.departure)
t.ok(!Number.isNaN(dep))
if (st.departure) {
t.equal(typeof st.departure, 'string')
const dep = +new Date(st.departure)
// note that this can be an ICE train
t.ok(isRoughlyEqual(14 * hour, +when, dep))
}
@ -312,8 +309,10 @@ test.skip('radar', async (t) => {
t.ok(Array.isArray(v.frames))
for (let f of v.frames) {
assertValidFrameStation(t, f.origin)
assertValidFrameStation(t, f.destination)
assertValidStation(t, f.origin, true)
t.strictEqual(f.origin.name.indexOf('(Berlin)'), -1)
assertValidStation(t, f.destination, true)
t.strictEqual(f.destination.name.indexOf('(Berlin)'), -1)
t.equal(typeof f.t, 'number')
}
}