-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathread-pathways.js
164 lines (138 loc) · 4.82 KB
/
read-pathways.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
'use strict'
const debug = require('debug')('gtfs-utils:read-pathways')
const debugNodes = require('debug')('gtfs-utils:read-pathways:nodes')
const inMemoryStore = require('./lib/in-memory-store')
const readStopStations = require('./lib/read-stop-stations')
const BIDIRECTIONAL = '1'
const readPathways = async function* (readFile, filters = {}, opt = {}) {
if (typeof readFile !== 'function') {
throw new TypeError('readFile must be a function')
}
filters = {
pathway: () => true,
stop: () => true,
...filters,
}
const {
pathway: pathwayFilter,
stop: stopFilter,
} = filters
if (typeof pathwayFilter !== 'function') {
throw new TypeError('filters.pathway must be a function')
}
const {
createStore,
} = {
createStore: inMemoryStore,
...opt,
}
const stations = await readStopStations(readFile, filters, createStore)
const pathways = createStore() // by node/stop ID
const pathwaysByFrom = createStore() // pathway IDs by from_stop_id
for await (let pw of await readFile('pathways')) {
if (!pathwayFilter(pw)) continue
if (
!pw.pathway_id
|| !pw.from_stop_id || !pw.to_stop_id || pw.from_stop_id === pw.to_stop_id
) {
// todo: debug-log invalid pathways
continue
}
await Promise.all([
pathways.set(pw.pathway_id, pw),
pathwaysByFrom.map(pw.from_stop_id, (pws) => {
if (!pws) return [pw.pathway_id]
pws.push(pw.pathway_id)
return pws
}),
])
}
const FORWARD = Symbol('forward') // along the "inherent" direction of a pathway
const REVERSE = Symbol('reverse') // in the opposite direction of a bidirectional pathway
const buildStationGraph = async (initialPw, initialDir, initialStationId) => {
const queue = [initialPw.pathway_id, initialDir]
const nodes = Object.create(null) // by stop ID
const seenPathways = new Set() // by pathway ID
let nrOfNodes = 0
while (queue.length > 0) {
const pwId = queue.shift()
const dir = queue.shift()
debug('pathway', pwId, 'direction', dir)
// todo: incorporate direction in lookup?
if (seenPathways.has(pwId)) continue // to prevent endless loops
seenPathways.add(pwId)
const pw = await pathways.get(pwId)
let fromNode = nodes[pw.from_stop_id]
if (!fromNode) {
nrOfNodes++
fromNode = nodes[pw.from_stop_id] = {
id: pw.from_stop_id,
connectedTo: Object.create(null), // by stop ID
}
}
debug('fromNode', fromNode)
let toNode = nodes[pw.to_stop_id]
if (!toNode) {
nrOfNodes++
toNode = nodes[pw.to_stop_id] = {
id: pw.to_stop_id,
connectedTo: Object.create(null), // by stop ID
}
}
debug('toNode', toNode)
let edges = fromNode.connectedTo[pw.to_stop_id]
if (!edges) {
edges = fromNode.connectedTo[pw.to_stop_id] = Object.create(null) // by pathway ID
}
if (!edges[pw.pathway_id]) edges[pw.pathway_id] = [pw, toNode]
if (pw.is_bidirectional === BIDIRECTIONAL) {
let reverseEdges = toNode.connectedTo[pw.from_stop_id]
if (!reverseEdges) {
reverseEdges = toNode.connectedTo[pw.from_stop_id] = Object.create(null) // by pathway ID
}
if (!reverseEdges[pw.pathway_id]) reverseEdges[pw.pathway_id] = [pw, fromNode]
}
debugNodes(nodes)
// find connecting edges, add them to the queue
const connectedStopId = dir === REVERSE ? pw.from_stop_id : pw.to_stop_id
const connectedStationId = await stations.get(connectedStopId)
if (connectedStationId === initialStationId) {
const connectingPwIds = (await pathwaysByFrom.get(connectedStopId)) || []
debug('queuing connecting pathways', ...connectingPwIds)
for (const pwId of connectingPwIds) {
if (seenPathways.has(pwId)) continue
if (queue.includes(pwId)) continue // todo: this is very expensive!
queue.push(pwId, FORWARD)
}
}
}
return nodes
}
const coveredStations = new Set() // by station ID
for await (const pw of pathways.values()) {
const t0 = Date.now()
const stationId = await stations.get(pw.from_stop_id)
debug('initial pathway', pw.pathway_id, 'from station ID', stationId)
// don't yield twice because we hit 2 pathways of the same graph
if (coveredStations.has(stationId)) {
debug(pw.pathway_id, stationId, 'already yielded before')
} else {
coveredStations.add(stationId)
const nodes = await buildStationGraph(pw, FORWARD, stationId)
yield [stationId, nodes[pw.from_stop_id], nodes]
}
if (pw.is_bidirectional === BIDIRECTIONAL) {
const stationId = await stations.get(pw.to_stop_id)
debug('bidirectional pathway', pw.pathway_id, 'to station ID', stationId)
if (coveredStations.has(stationId)) {
debug(pw.pathway_id, stationId, 'already yielded before')
} else {
coveredStations.add(stationId)
const nodes = await buildStationGraph(pw, REVERSE, stationId)
yield [stationId, nodes[pw.to_stop_id], nodes]
}
}
debug(pw.pathway_id, Date.now() - t0, 'ms')
}
}
module.exports = readPathways