mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-02-23 15:19:35 +02:00
query live movements in an area
This commit is contained in:
parent
33417a6e61
commit
3fb0943680
7 changed files with 110 additions and 31 deletions
|
@ -8,5 +8,6 @@ module.exports = {
|
||||||
address: require('./address'),
|
address: require('./address'),
|
||||||
poi: require('./poi'),
|
poi: require('./poi'),
|
||||||
location: require('./location'),
|
location: require('./location'),
|
||||||
locationFilter: require('./location-filter')
|
locationFilter: require('./location-filter'),
|
||||||
|
rectangle: require('./rectangle')
|
||||||
}
|
}
|
||||||
|
|
16
format/rectangle.js
Normal file
16
format/rectangle.js
Normal 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
|
43
index.js
43
index.js
|
@ -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
|
module.exports = createClient
|
||||||
|
|
|
@ -21,6 +21,7 @@ const formatPoi = require('../format/poi')
|
||||||
const formatStation = require('../format/station')
|
const formatStation = require('../format/station')
|
||||||
const formatTime = require('../format/time')
|
const formatTime = require('../format/time')
|
||||||
const formatLocation = require('../format/location')
|
const formatLocation = require('../format/location')
|
||||||
|
const formatRectangle = require('../format/rectangle')
|
||||||
|
|
||||||
const id = x => x
|
const id = x => x
|
||||||
|
|
||||||
|
@ -52,7 +53,8 @@ const defaultProfile = {
|
||||||
formatPoi,
|
formatPoi,
|
||||||
formatStation,
|
formatStation,
|
||||||
formatTime,
|
formatTime,
|
||||||
formatLocation
|
formatLocation,
|
||||||
|
formatRectangle
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = defaultProfile
|
module.exports = defaultProfile
|
||||||
|
|
|
@ -27,7 +27,7 @@ const createParseMovement = (profile, locations, lines, remarks) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = {
|
const res = {
|
||||||
direction: m.dirTxt,
|
direction: profile.parseStationName(m.dirTxt),
|
||||||
line: lines[m.prodX] || null,
|
line: lines[m.prodX] || null,
|
||||||
coordinates: m.pos ? {
|
coordinates: m.pos ? {
|
||||||
latitude: m.pos.y / 1000000,
|
latitude: m.pos.y / 1000000,
|
||||||
|
|
42
test/util.js
42
test/util.js
|
@ -3,31 +3,52 @@
|
||||||
const isRoughlyEqual = require('is-roughly-equal')
|
const isRoughlyEqual = require('is-roughly-equal')
|
||||||
const floor = require('floordate')
|
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(s.type, 'station')
|
||||||
t.equal(typeof s.id, 'string')
|
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) => {
|
const assertValidPoi = (t, p) => {
|
||||||
|
t.equal(typeof p.type, 'string')
|
||||||
t.equal(p.type, 'poi')
|
t.equal(p.type, 'poi')
|
||||||
t.equal(typeof p.id, 'string')
|
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) => {
|
const assertValidAddress = (t, a) => {
|
||||||
|
t.equal(typeof a.type, 'string')
|
||||||
t.equal(a.type, 'address')
|
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) => {
|
const assertValidLocation = (t, l) => {
|
||||||
t.equal(typeof l.type, 'string')
|
|
||||||
if (l.type === 'station') assertValidStation(t, l)
|
if (l.type === 'station') assertValidStation(t, l)
|
||||||
else if (l.type === 'poi') assertValidPoi(t, l)
|
else if (l.type === 'poi') assertValidPoi(t, l)
|
||||||
else if (l.type === 'address') assertValidAddress(t, l)
|
else if (l.type === 'address') assertValidAddress(t, l)
|
||||||
else t.fail('invalid type ' + l.type)
|
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) => {
|
const isValidMode = (m) => {
|
||||||
|
@ -40,7 +61,6 @@ const isValidMode = (m) => {
|
||||||
const assertValidLine = (t, l) => {
|
const assertValidLine = (t, l) => {
|
||||||
t.equal(l.type, 'line')
|
t.equal(l.type, 'line')
|
||||||
t.equal(typeof l.name, 'string')
|
t.equal(typeof l.name, 'string')
|
||||||
if (!isValidMode(l.mode)) console.error(l)
|
|
||||||
t.ok(isValidMode(l.mode), 'invalid mode ' + l.mode)
|
t.ok(isValidMode(l.mode), 'invalid mode ' + l.mode)
|
||||||
t.equal(typeof l.product, 'string')
|
t.equal(typeof l.product, 'string')
|
||||||
t.equal(l.public, true)
|
t.equal(l.public, true)
|
||||||
|
@ -50,14 +70,14 @@ const isValidDateTime = (w) => {
|
||||||
return !Number.isNaN(+new Date(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 ('arrival' in s) t.ok(isValidDateTime(s.arrival))
|
||||||
if ('departure' in s) t.ok(isValidDateTime(s.departure))
|
if ('departure' in s) t.ok(isValidDateTime(s.departure))
|
||||||
if (!('arrival' in s) && !('departure' in s)) {
|
if (!('arrival' in s) && !('departure' in s)) {
|
||||||
t.fail('stopover doesn\'t contain arrival or departure')
|
t.fail('stopover doesn\'t contain arrival or departure')
|
||||||
}
|
}
|
||||||
t.ok(s.station)
|
t.ok(s.station)
|
||||||
assertValidStation(t, s.station)
|
assertValidStation(t, s.station, coordsOptional)
|
||||||
}
|
}
|
||||||
|
|
||||||
const minute = 60 * 1000
|
const minute = 60 * 1000
|
||||||
|
@ -86,5 +106,5 @@ module.exports = {
|
||||||
assertValidLine,
|
assertValidLine,
|
||||||
isValidDateTime,
|
isValidDateTime,
|
||||||
assertValidStopover,
|
assertValidStopover,
|
||||||
when, isValidWhen, assertValidWhen
|
hour, when, isValidWhen, assertValidWhen
|
||||||
}
|
}
|
||||||
|
|
31
test/vbb.js
31
test/vbb.js
|
@ -269,8 +269,7 @@ test('locations', async (t) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// todo
|
test('radar', async (t) => {
|
||||||
test.skip('radar', async (t) => {
|
|
||||||
const vehicles = await client.radar(52.52411, 13.41002, 52.51942, 13.41709, {
|
const vehicles = await client.radar(52.52411, 13.41002, 52.51942, 13.41709, {
|
||||||
duration: 5 * 60, when
|
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(v.coordinates.longitude <= 15, 'vehicle is too far away')
|
||||||
|
|
||||||
t.ok(Array.isArray(v.nextStops))
|
t.ok(Array.isArray(v.nextStops))
|
||||||
for (let s of v.nextStops) {
|
for (let st of v.nextStops) {
|
||||||
assertValidFrameStation(t, s.station)
|
assertValidStopover(t, st, true)
|
||||||
if (!s.arrival && !s.departure)
|
t.strictEqual(st.station.name.indexOf('(Berlin)'), -1)
|
||||||
t.ifError(new Error('neither arrival nor departure return'))
|
|
||||||
if (s.arrival) {
|
if (st.arrival) {
|
||||||
t.equal(typeof s.arrival, 'string')
|
t.equal(typeof st.arrival, 'string')
|
||||||
const arr = +new Date(s.arrival)
|
const arr = +new Date(st.arrival)
|
||||||
t.ok(!Number.isNaN(arr))
|
|
||||||
// note that this can be an ICE train
|
// note that this can be an ICE train
|
||||||
t.ok(isRoughlyEqual(14 * hour, +when, arr))
|
t.ok(isRoughlyEqual(14 * hour, +when, arr))
|
||||||
}
|
}
|
||||||
if (s.departure) {
|
if (st.departure) {
|
||||||
t.equal(typeof s.departure, 'string')
|
t.equal(typeof st.departure, 'string')
|
||||||
const dep = +new Date(s.departure)
|
const dep = +new Date(st.departure)
|
||||||
t.ok(!Number.isNaN(dep))
|
|
||||||
// note that this can be an ICE train
|
// note that this can be an ICE train
|
||||||
t.ok(isRoughlyEqual(14 * hour, +when, dep))
|
t.ok(isRoughlyEqual(14 * hour, +when, dep))
|
||||||
}
|
}
|
||||||
|
@ -312,8 +309,10 @@ test.skip('radar', async (t) => {
|
||||||
|
|
||||||
t.ok(Array.isArray(v.frames))
|
t.ok(Array.isArray(v.frames))
|
||||||
for (let f of v.frames) {
|
for (let f of v.frames) {
|
||||||
assertValidFrameStation(t, f.origin)
|
assertValidStation(t, f.origin, true)
|
||||||
assertValidFrameStation(t, f.destination)
|
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')
|
t.equal(typeof f.t, 'number')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue