mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-02-23 23:29:35 +02:00
merge next into tests-rewrite
This commit is contained in:
commit
1bb1ce4f8b
17 changed files with 192 additions and 43 deletions
|
@ -1,5 +1,11 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## `2.7.0`
|
||||||
|
|
||||||
|
- `journeys()`: `polylines` option
|
||||||
|
- `journeyLeg()`: `polyline` option
|
||||||
|
- `radar()`: `polylines` option
|
||||||
|
|
||||||
## `2.6.0`
|
## `2.6.0`
|
||||||
|
|
||||||
- 5d10d76 journey legs: parse cycle
|
- 5d10d76 journey legs: parse cycle
|
||||||
|
|
|
@ -118,4 +118,66 @@ The response looked like this:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you pass `polyline: true`, the leg will have a `polyline` field, containing an encoded shape. You can use e.g. [`@mapbox/polyline`](https://www.npmjs.com/package/@mapbox/polyline) to decode it.
|
### `polyline` option
|
||||||
|
|
||||||
|
If you pass `polyline: true`, the leg will have a `polyline` field, containing a [GeoJSON](http://geojson.org) [`FeatureCollection`](https://tools.ietf.org/html/rfc7946#section-3.3) of [`Point`s](https://tools.ietf.org/html/rfc7946#appendix-A.1). Every `Point` next to a station will have `properties` containing the station's metadata.
|
||||||
|
|
||||||
|
We'll look at an example for *U6* from *Alt-Mariendorf* to *Alt-Tegel*, taken from the [VBB profile](../p/vbb):
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
type: 'station',
|
||||||
|
id: '900000070301',
|
||||||
|
name: 'U Alt-Mariendorf',
|
||||||
|
/* … */
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [13.3875, 52.43993] // longitude, latitude
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* … */
|
||||||
|
{
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
type: 'station',
|
||||||
|
id: '900000017101',
|
||||||
|
name: 'U Mehringdamm',
|
||||||
|
/* … */
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [13.38892, 52.49448] // longitude, latitude
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* … */
|
||||||
|
{
|
||||||
|
// intermediate point, without associated station
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [13.28599, 52.58742] // longitude, latitude
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
type: 'station',
|
||||||
|
id: '900000089301',
|
||||||
|
name: 'U Alt-Tegel',
|
||||||
|
/* … */
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [13.28406, 52.58915] // longitude, latitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -262,4 +262,4 @@ departure of last journey 2017-12-17T19:07:00.000+01:00
|
||||||
departure of first (later) journey 2017-12-17T19:19:00.000+01:00
|
departure of first (later) journey 2017-12-17T19:19:00.000+01:00
|
||||||
```
|
```
|
||||||
|
|
||||||
If you pass `polylines: true`, each journey leg will have a `polyline` field, containing an encoded shape. You can use e.g. [`@mapbox/polyline`](https://www.npmjs.com/package/@mapbox/polyline) to decode it.
|
If you pass `polylines: true`, each journey leg will have a `polyline` field. Refer to [the section in the `journeyLeg()` docs](journey-leg.md#polyline-option) for details.
|
||||||
|
|
|
@ -11,6 +11,7 @@ With `opt`, you can override the default options, which look like this:
|
||||||
results: 256, // maximum number of vehicles
|
results: 256, // maximum number of vehicles
|
||||||
duration: 30, // compute frames for the next n seconds
|
duration: 30, // compute frames for the next n seconds
|
||||||
frames: 3, // nr of frames to compute
|
frames: 3, // nr of frames to compute
|
||||||
|
polylines: false // return a track shape for each vehicle?
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -161,3 +162,5 @@ The response may look like this:
|
||||||
} ]
|
} ]
|
||||||
}, /* … */ ]
|
}, /* … */ ]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you pass `polylines: true`, each journey leg will have a `polyline` field, as documented in [the corresponding section in the `journeyLeg()` docs](journey-leg.md#polyline-option), with the exception that station info is missing.
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const formatLocation = (profile, l) => {
|
const formatLocation = (profile, l, name = 'location') => {
|
||||||
if ('string' === typeof l) return profile.formatStation(l)
|
if ('string' === typeof l) return profile.formatStation(l)
|
||||||
if ('object' === typeof l && !Array.isArray(l)) {
|
if ('object' === typeof l && !Array.isArray(l)) {
|
||||||
if (l.type === 'station') return profile.formatStation(l.id)
|
if (l.type === 'station') return profile.formatStation(l.id)
|
||||||
if ('string' === typeof l.id) return profile.formatPoi(l)
|
if ('string' === typeof l.id) return profile.formatPoi(l)
|
||||||
if ('string' === typeof l.address) return profile.formatAddress(l)
|
if ('string' === typeof l.address) return profile.formatAddress(l)
|
||||||
throw new Error('invalid location type: ' + l.type)
|
if (!l.type) throw new Error(`missing ${name}.type`)
|
||||||
|
throw new Error(`invalid ${name}.type: ${l.type}`)
|
||||||
}
|
}
|
||||||
throw new Error('valid station, address or poi required.')
|
throw new Error(name + ': valid station, address or poi required.')
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = formatLocation
|
module.exports = formatLocation
|
||||||
|
|
|
@ -16,12 +16,14 @@ const createFormatProductsFilter = (profile) => {
|
||||||
if (!isObj(filter)) throw new Error('products filter must be an object')
|
if (!isObj(filter)) throw new Error('products filter must be an object')
|
||||||
filter = Object.assign({}, defaultProducts, filter)
|
filter = Object.assign({}, defaultProducts, filter)
|
||||||
|
|
||||||
let res = 0
|
let res = 0, products = 0
|
||||||
for (let product in filter) {
|
for (let product in filter) {
|
||||||
if (!hasProp(filter, product) || filter[product] !== true) continue
|
if (!hasProp(filter, product) || filter[product] !== true) continue
|
||||||
if (!byProduct[product]) throw new Error('unknown product ' + product)
|
if (!byProduct[product]) throw new Error('unknown product ' + product)
|
||||||
|
products++
|
||||||
for (let bitmask of byProduct[product].bitmasks) res += bitmask
|
for (let bitmask of byProduct[product].bitmasks) res += bitmask
|
||||||
}
|
}
|
||||||
|
if (products === 0) throw new Error('no products used')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'PROD',
|
type: 'PROD',
|
||||||
|
|
36
index.js
36
index.js
|
@ -31,7 +31,8 @@ const createClient = (profile, request = _request) => {
|
||||||
direction: null, // only show departures heading to this station
|
direction: null, // only show departures heading to this station
|
||||||
duration: 10 // show departures for the next n minutes
|
duration: 10 // show departures for the next n minutes
|
||||||
}, opt)
|
}, opt)
|
||||||
opt.when = opt.when || new Date()
|
opt.when = new Date(opt.when || Date.now())
|
||||||
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
||||||
const products = profile.formatProductsFilter(opt.products || {})
|
const products = profile.formatProductsFilter(opt.products || {})
|
||||||
|
|
||||||
const dir = opt.direction ? profile.formatStation(opt.direction) : null
|
const dir = opt.direction ? profile.formatStation(opt.direction) : null
|
||||||
|
@ -57,8 +58,8 @@ const createClient = (profile, request = _request) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const journeys = (from, to, opt = {}) => {
|
const journeys = (from, to, opt = {}) => {
|
||||||
from = profile.formatLocation(profile, from)
|
from = profile.formatLocation(profile, from, 'from')
|
||||||
to = profile.formatLocation(profile, to)
|
to = profile.formatLocation(profile, to, 'to')
|
||||||
|
|
||||||
if (('earlierThan' in opt) && ('laterThan' in opt)) {
|
if (('earlierThan' in opt) && ('laterThan' in opt)) {
|
||||||
throw new Error('opt.laterThan and opt.laterThan are mutually exclusive.')
|
throw new Error('opt.laterThan and opt.laterThan are mutually exclusive.')
|
||||||
|
@ -95,8 +96,9 @@ const createClient = (profile, request = _request) => {
|
||||||
tickets: false, // return tickets?
|
tickets: false, // return tickets?
|
||||||
polylines: false // return leg shapes?
|
polylines: false // return leg shapes?
|
||||||
}, opt)
|
}, opt)
|
||||||
if (opt.via) opt.via = profile.formatLocation(profile, opt.via)
|
if (opt.via) opt.via = profile.formatLocation(profile, opt.via, 'opt.via')
|
||||||
opt.when = opt.when || new Date()
|
opt.when = new Date(opt.when || Date.now())
|
||||||
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
||||||
|
|
||||||
const filters = [
|
const filters = [
|
||||||
profile.formatProductsFilter(opt.products || {})
|
profile.formatProductsFilter(opt.products || {})
|
||||||
|
@ -147,10 +149,7 @@ const createClient = (profile, request = _request) => {
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
if (!Array.isArray(d.outConL)) return []
|
if (!Array.isArray(d.outConL)) return []
|
||||||
|
|
||||||
let polylines = []
|
const polylines = opt.polylines && d.common.polyL || []
|
||||||
if (opt.polylines && Array.isArray(d.common.polyL)) {
|
|
||||||
polylines = d.common.polyL
|
|
||||||
}
|
|
||||||
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks, polylines)
|
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks, polylines)
|
||||||
|
|
||||||
if (!journeys.earlierRef) journeys.earlierRef = d.outCtxScrB
|
if (!journeys.earlierRef) journeys.earlierRef = d.outCtxScrB
|
||||||
|
@ -281,7 +280,8 @@ const createClient = (profile, request = _request) => {
|
||||||
passedStations: true, // return stations on the way?
|
passedStations: true, // return stations on the way?
|
||||||
polyline: false
|
polyline: false
|
||||||
}, opt)
|
}, opt)
|
||||||
opt.when = opt.when || new Date()
|
opt.when = new Date(opt.when || Date.now())
|
||||||
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
||||||
|
|
||||||
return request(profile, {
|
return request(profile, {
|
||||||
cfg: {polyEnc: 'GPA'},
|
cfg: {polyEnc: 'GPA'},
|
||||||
|
@ -295,10 +295,7 @@ const createClient = (profile, request = _request) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
let polylines = []
|
const polylines = opt.polyline && d.common.polyL || []
|
||||||
if (opt.polyline && Array.isArray(d.common.polyL)) {
|
|
||||||
polylines = d.common.polyL
|
|
||||||
}
|
|
||||||
const parse = profile.parseJourneyLeg(profile, d.locations, d.lines, d.remarks, polylines)
|
const parse = profile.parseJourneyLeg(profile, d.locations, d.lines, d.remarks, polylines)
|
||||||
|
|
||||||
const leg = { // pretend the leg is contained in a journey
|
const leg = { // pretend the leg is contained in a journey
|
||||||
|
@ -316,14 +313,18 @@ const createClient = (profile, request = _request) => {
|
||||||
if ('number' !== typeof west) throw new Error('west 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 south) throw new Error('south must be a number.')
|
||||||
if ('number' !== typeof east) throw new Error('east must be a number.')
|
if ('number' !== typeof east) throw new Error('east must be a number.')
|
||||||
|
if (north <= south) throw new Error('north must be larger than south.')
|
||||||
|
if (east <= west) throw new Error('east must be larger than west.')
|
||||||
|
|
||||||
opt = Object.assign({
|
opt = Object.assign({
|
||||||
results: 256, // maximum number of vehicles
|
results: 256, // maximum number of vehicles
|
||||||
duration: 30, // compute frames for the next n seconds
|
duration: 30, // compute frames for the next n seconds
|
||||||
frames: 3, // nr of frames to compute
|
frames: 3, // nr of frames to compute
|
||||||
products: null // optionally an object of booleans
|
products: null, // optionally an object of booleans
|
||||||
|
polylines: false // return a track shape for each vehicle?
|
||||||
}, opt || {})
|
}, opt || {})
|
||||||
opt.when = opt.when || new Date()
|
opt.when = new Date(opt.when || Date.now())
|
||||||
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
||||||
|
|
||||||
const durationPerStep = opt.duration / Math.max(opt.frames, 1) * 1000
|
const durationPerStep = opt.duration / Math.max(opt.frames, 1) * 1000
|
||||||
return request(profile, {
|
return request(profile, {
|
||||||
|
@ -347,7 +348,8 @@ const createClient = (profile, request = _request) => {
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
if (!Array.isArray(d.jnyL)) return []
|
if (!Array.isArray(d.jnyL)) return []
|
||||||
|
|
||||||
const parse = profile.parseMovement(profile, d.locations, d.lines, d.remarks)
|
const polylines = opt.polyline && d.common.polyL || []
|
||||||
|
const parse = profile.parseMovement(profile, d.locations, d.lines, d.remarks, polylines)
|
||||||
return d.jnyL.map(parse)
|
return d.jnyL.map(parse)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ const parseJourneyLeg = require('../parse/journey-leg')
|
||||||
const parseJourney = require('../parse/journey')
|
const parseJourney = require('../parse/journey')
|
||||||
const parseLine = require('../parse/line')
|
const parseLine = require('../parse/line')
|
||||||
const parseLocation = require('../parse/location')
|
const parseLocation = require('../parse/location')
|
||||||
|
const parsePolyline = require('../parse/polyline')
|
||||||
const parseMovement = require('../parse/movement')
|
const parseMovement = require('../parse/movement')
|
||||||
const parseNearby = require('../parse/nearby')
|
const parseNearby = require('../parse/nearby')
|
||||||
const parseOperator = require('../parse/operator')
|
const parseOperator = require('../parse/operator')
|
||||||
|
@ -42,6 +43,7 @@ const defaultProfile = {
|
||||||
parseLine,
|
parseLine,
|
||||||
parseStationName: id,
|
parseStationName: id,
|
||||||
parseLocation,
|
parseLocation,
|
||||||
|
parsePolyline,
|
||||||
parseMovement,
|
parseMovement,
|
||||||
parseNearby,
|
parseNearby,
|
||||||
parseOperator,
|
parseOperator,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const crypto = require('crypto')
|
const createHash = require('create-hash')
|
||||||
let captureStackTrace = () => {}
|
let captureStackTrace = () => {}
|
||||||
if (process.env.NODE_DEBUG === 'hafas-client') {
|
if (process.env.NODE_DEBUG === 'hafas-client') {
|
||||||
captureStackTrace = require('capture-stack-trace')
|
captureStackTrace = require('capture-stack-trace')
|
||||||
|
@ -9,7 +9,7 @@ const {stringify} = require('query-string')
|
||||||
const Promise = require('pinkie-promise')
|
const Promise = require('pinkie-promise')
|
||||||
const {fetch} = require('fetch-ponyfill')({Promise})
|
const {fetch} = require('fetch-ponyfill')({Promise})
|
||||||
|
|
||||||
const md5 = input => crypto.createHash('md5').update(input).digest()
|
const md5 = input => createHash('md5').update(input).digest()
|
||||||
|
|
||||||
const request = (profile, data) => {
|
const request = (profile, data) => {
|
||||||
const body = profile.transformReqBody({lang: 'en', svcReqL: [data]})
|
const body = profile.transformReqBody({lang: 'en', svcReqL: [data]})
|
||||||
|
|
|
@ -16,6 +16,7 @@ const types = {
|
||||||
parseLine: 'function',
|
parseLine: 'function',
|
||||||
parseStationName: 'function',
|
parseStationName: 'function',
|
||||||
parseLocation: 'function',
|
parseLocation: 'function',
|
||||||
|
parsePolyline: 'function',
|
||||||
parseMovement: 'function',
|
parseMovement: 'function',
|
||||||
parseNearby: 'function',
|
parseNearby: 'function',
|
||||||
parseOperator: 'function',
|
parseOperator: 'function',
|
||||||
|
|
|
@ -10,7 +10,7 @@ const formatLoyaltyCard = require('./loyalty-cards').format
|
||||||
const transformReqBody = (body) => {
|
const transformReqBody = (body) => {
|
||||||
body.client = {id: 'DB', v: '16040000', type: 'IPH', name: 'DB Navigator'}
|
body.client = {id: 'DB', v: '16040000', type: 'IPH', name: 'DB Navigator'}
|
||||||
body.ext = 'DB.R15.12.a'
|
body.ext = 'DB.R15.12.a'
|
||||||
body.ver = '1.15'
|
body.ver = '1.16'
|
||||||
body.auth = {type: 'AID', aid: 'n91dB8Z77MLdoR0K'}
|
body.auth = {type: 'AID', aid: 'n91dB8Z77MLdoR0K'}
|
||||||
|
|
||||||
return body
|
return body
|
||||||
|
@ -34,8 +34,8 @@ const transformJourneysQuery = (query, opt) => {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
const createParseJourney = (profile, stations, lines, remarks) => {
|
const createParseJourney = (profile, stations, lines, remarks, polylines) => {
|
||||||
const parseJourney = _createParseJourney(profile, stations, lines, remarks)
|
const parseJourney = _createParseJourney(profile, stations, lines, remarks, polylines)
|
||||||
|
|
||||||
// todo: j.sotRating, j.conSubscr, j.isSotCon, j.showARSLink, k.sotCtxt
|
// todo: j.sotRating, j.conSubscr, j.isSotCon, j.showARSLink, k.sotCtxt
|
||||||
// todo: j.conSubscr, j.showARSLink, j.useableTime
|
// todo: j.conSubscr, j.showARSLink, j.useableTime
|
||||||
|
@ -102,7 +102,7 @@ const dbProfile = {
|
||||||
|
|
||||||
formatStation,
|
formatStation,
|
||||||
|
|
||||||
journeyLeg: true
|
journeyLeg: true // todo: #49
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = dbProfile
|
module.exports = dbProfile
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "hafas-client",
|
"name": "hafas-client",
|
||||||
"description": "JavaScript client for HAFAS public transport APIs.",
|
"description": "JavaScript client for HAFAS public transport APIs.",
|
||||||
"version": "2.6.0",
|
"version": "2.7.3",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
|
@ -32,8 +32,11 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mapbox/polyline": "^1.0.0",
|
||||||
"capture-stack-trace": "^1.0.0",
|
"capture-stack-trace": "^1.0.0",
|
||||||
|
"create-hash": "^1.2.0",
|
||||||
"fetch-ponyfill": "^6.0.0",
|
"fetch-ponyfill": "^6.0.0",
|
||||||
|
"gps-distance": "0.0.4",
|
||||||
"lodash": "^4.17.5",
|
"lodash": "^4.17.5",
|
||||||
"luxon": "^0.5.8",
|
"luxon": "^0.5.8",
|
||||||
"p-throttle": "^1.1.0",
|
"p-throttle": "^1.1.0",
|
||||||
|
|
|
@ -36,9 +36,10 @@ const createParseJourneyLeg = (profile, stations, lines, remarks, polylines) =>
|
||||||
|
|
||||||
if (pt.jny && pt.jny.polyG) {
|
if (pt.jny && pt.jny.polyG) {
|
||||||
let p = pt.jny.polyG.polyXL
|
let p = pt.jny.polyG.polyXL
|
||||||
p = p && polylines[p[0]]
|
p = Array.isArray(p) && polylines[p[0]]
|
||||||
// todo: there can be >1 polyline
|
// todo: there can be >1 polyline
|
||||||
res.polyline = p && p.crdEncYX || null
|
const parse = profile.parsePolyline(stations)
|
||||||
|
res.polyline = p && parse(p) || null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pt.type === 'WALK') {
|
if (pt.type === 'WALK') {
|
||||||
|
|
|
@ -19,7 +19,7 @@ const parseLocation = (profile, l, lines) => {
|
||||||
const station = {
|
const station = {
|
||||||
type: 'station',
|
type: 'station',
|
||||||
id: l.extId,
|
id: l.extId,
|
||||||
name: profile.parseStationName(l.name),
|
name: l.name ? profile.parseStationName(l.name) : null,
|
||||||
location: 'number' === typeof res.latitude ? res : null
|
location: 'number' === typeof res.latitude ? res : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const createParseMovement = (profile, locations, lines, remarks) => {
|
const createParseMovement = (profile, locations, lines, remarks, polylines = []) => {
|
||||||
// todo: what is m.dirGeo? maybe the speed?
|
// todo: what is m.dirGeo? maybe the speed?
|
||||||
// todo: what is m.stopL?
|
// todo: what is m.stopL?
|
||||||
// todo: what is m.proc? wut?
|
// todo: what is m.proc? wut?
|
||||||
// todo: what is m.pos?
|
// todo: what is m.pos?
|
||||||
// todo: what is m.ani.dirGeo[n]? maybe the speed?
|
// todo: what is m.ani.dirGeo[n]? maybe the speed?
|
||||||
// todo: what is m.ani.proc[n]? wut?
|
// todo: what is m.ani.proc[n]? wut?
|
||||||
// todo: how does m.ani.poly work?
|
|
||||||
const parseMovement = (m) => {
|
const parseMovement = (m) => {
|
||||||
const pStopover = profile.parseStopover(profile, locations, lines, remarks, m.date)
|
const pStopover = profile.parseStopover(profile, locations, lines, remarks, m.date)
|
||||||
|
|
||||||
const res = {
|
const res = {
|
||||||
direction: profile.parseStationName(m.dirTxt),
|
direction: profile.parseStationName(m.dirTxt),
|
||||||
|
journeyId: m.jid || null,
|
||||||
trip: m.jid && +m.jid.split('|')[1] || null, // todo: this seems brittle
|
trip: m.jid && +m.jid.split('|')[1] || null, // todo: this seems brittle
|
||||||
line: lines[m.prodX] || null,
|
line: lines[m.prodX] || null,
|
||||||
location: m.pos ? {
|
location: m.pos ? {
|
||||||
|
@ -24,7 +24,8 @@ const createParseMovement = (profile, locations, lines, remarks) => {
|
||||||
frames: []
|
frames: []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m.ani && Array.isArray(m.ani.mSec)) {
|
if (m.ani) {
|
||||||
|
if (Array.isArray(m.ani.mSec)) {
|
||||||
for (let i = 0; i < m.ani.mSec.length; i++) {
|
for (let i = 0; i < m.ani.mSec.length; i++) {
|
||||||
res.frames.push({
|
res.frames.push({
|
||||||
origin: locations[m.ani.fLocX[i]] || null,
|
origin: locations[m.ani.fLocX[i]] || null,
|
||||||
|
@ -34,6 +35,18 @@ const createParseMovement = (profile, locations, lines, remarks) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m.ani.poly) {
|
||||||
|
const parse = profile.parsePolyline(locations)
|
||||||
|
res.polyline = parse(m.ani.poly)
|
||||||
|
} else if (m.ani.polyG) {
|
||||||
|
let p = m.ani.polyG.polyXL
|
||||||
|
p = Array.isArray(p) && polylines[p[0]]
|
||||||
|
// todo: there can be >1 polyline
|
||||||
|
const parse = profile.parsePolyline(locations)
|
||||||
|
res.polyline = p && parse(p) || null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
return parseMovement
|
return parseMovement
|
||||||
|
|
53
parse/polyline.js
Normal file
53
parse/polyline.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const {toGeoJSON} = require('@mapbox/polyline')
|
||||||
|
const distance = require('gps-distance')
|
||||||
|
|
||||||
|
const createParsePolyline = (locations) => {
|
||||||
|
// todo: what is p.delta?
|
||||||
|
// todo: what is p.type?
|
||||||
|
// todo: what is p.crdEncS?
|
||||||
|
// todo: what is p.crdEncF?
|
||||||
|
const parsePolyline = (p) => {
|
||||||
|
const shape = toGeoJSON(p.crdEncYX)
|
||||||
|
if (shape.coordinates.length === 0) return null
|
||||||
|
|
||||||
|
const res = shape.coordinates.map(crd => ({
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: crd
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (Array.isArray(p.ppLocRefL)) {
|
||||||
|
for (let ref of p.ppLocRefL) {
|
||||||
|
const p = res[ref.ppIdx]
|
||||||
|
const loc = locations[ref.locX]
|
||||||
|
if (p && loc) p.properties = loc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Often there is one more point right next to each point at a station.
|
||||||
|
// We filter them here if they are < 5m from each other.
|
||||||
|
for (let i = 1; i < res.length; i++) {
|
||||||
|
const p1 = res[i - 1].geometry.coordinates
|
||||||
|
const p2 = res[i].geometry.coordinates
|
||||||
|
const d = distance(p1[1], p1[0], p2[1], p2[0])
|
||||||
|
if (d >= .005) continue
|
||||||
|
const l1 = Object.keys(res[i - 1].properties).length
|
||||||
|
const l2 = Object.keys(res[i].properties).length
|
||||||
|
if (l1 === 0 && l2 > 0) res.splice(i - 1, 1)
|
||||||
|
else if (l2 === 0 && l1 > 0) res.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsePolyline
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createParsePolyline
|
|
@ -13,7 +13,7 @@ HAFAS endpoint | wrapper library | docs | example code | source code
|
||||||
[](https://www.npmjs.com/package/hafas-client)
|
[](https://www.npmjs.com/package/hafas-client)
|
||||||
[](https://travis-ci.org/public-transport/hafas-client)
|
[](https://travis-ci.org/public-transport/hafas-client)
|
||||||

|

|
||||||
[](https://gitter.im/derhuerst)
|
[](https://gitter.im/public-transport/Lobby)
|
||||||
[](https://patreon.com/derhuerst)
|
[](https://patreon.com/derhuerst)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue