-
Notifications
You must be signed in to change notification settings - Fork 3
/
data.js
264 lines (225 loc) · 7.46 KB
/
data.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// @flow
import lonlat from '@conveyal/lonlat'
import fetch, {fetchMultiple} from '@conveyal/woonerf/fetch'
import Leaflet from 'leaflet'
import {ACCESSIBILITY_IS_EMPTY, ACCESSIBILITY_IS_LOADING} from '../constants'
import geocode from './geocode'
import {getAsObject as getHash} from '../utils/hash'
import {
setEnd,
setOrigin,
setStart,
updateMap
} from '../actions'
import {loadGrid} from './grid'
import type {LonLat} from '../types'
export function initialize () {
return (dispatch: Dispatch, getState: any) => {
const state = getState()
const qs = getHash()
const origins = state.data.origins
const currentZoom = qs.zoom ? parseInt(qs.zoom, 10) : 11
dispatch(updateMap({zoom: currentZoom}))
origins.map((origin) => {
dispatch(
qs.start
? setOrigin({name: origin.name, accessibility: ACCESSIBILITY_IS_LOADING})
: setOrigin({name: origin.name, accessibility: ACCESSIBILITY_IS_EMPTY})
)
})
state.data.grids.map((grid) => {
dispatch(loadGrid(grid.name, state.data.gridsUrl))
})
if (qs.start) {
dispatch(setStart({label: qs.start}))
dispatch(geocode(qs.start, (feature) => {
const originLonlat = lonlat(feature.center)
dispatch(setStart({
label: qs.start,
position: originLonlat
}))
dispatch(loadOrigins(origins, originLonlat, currentZoom))
}))
} else {
dispatch(loadOrigins(origins))
}
if (qs.end) {
dispatch(setEnd({label: qs.end}))
dispatch(geocode(qs.end, (feature) => {
const position = lonlat(feature.center)
dispatch([
setEnd({
label: qs.end,
position: lonlat(feature.center)
}),
fetchDestinationDataForLonLat(position)
])
if (!qs.start) {
dispatch(updateMap({centerCoordinates: lonlat.toLeaflet(feature.center)}))
}
}))
}
}
}
const loadOrigins = (origins, originLonlat?: LonLat, currentZoom?: number) =>
origins.map(origin => loadOrigin(origin, originLonlat, currentZoom))
const loadOrigin = (origin, originLonlat?: LonLat, currentZoom?: number) =>
fetch({
url: `${origin.url}/query.json`,
next (response) {
const query = response.value
if (originLonlat && currentZoom) {
return fetchDataForOrigin({...origin, query}, originLonlat, currentZoom)
} else {
return setOrigin({...origin, query})
}
}
})
export const fetchDataForLonLat = (originLonLat: LonLat) =>
(dispatch: Dispatch, getState: any) => {
const state = getState()
const currentZoom = state.map.zoom
dispatch(state.data.origins.map(origin =>
fetchDataForOrigin(origin, originLonLat, currentZoom)))
}
const fetchDataForOrigin = (origin, originLonlat, currentZoom) => {
const originPoint = getPointForLonLat(originLonlat, currentZoom, origin.query)
const originIndex = originPoint.x + originPoint.y * origin.query.width
return fetchMultiple({
fetches: [{
url: `${origin.url}/${originIndex}_times.dat`
}, {
url: `${origin.url}/${originIndex}_paths.dat`
}],
next: ([timesResponse, pathsResponse]) => {
const travelTimeSurface = parseTimesData(timesResponse.value)
const nTargets = travelTimeSurface.width * travelTimeSurface.height
const {pathLists, targets} = parsePathsData(pathsResponse.value, nTargets, 120)
debugger
validatePathLists(pathLists, origin.query.transitiveData)
return setOrigin({
...origin,
originPoint,
pathLists,
targets,
travelTimeSurface
})
}
})
}
export const fetchDestinationDataForLonLat = (position: LonLat) =>
(dispatch: Dispatch, getState: any) => {
const state = getState()
const currentZoom = state.map.zoom
dispatch(state.data.origins.map(origin =>
dispatch(fetchDestinationDataForOrigin(origin, position, currentZoom))))
}
const fetchDestinationDataForOrigin = (origin, position, currentZoom) => {
const point = getPointForLonLat(position, currentZoom, origin.query)
const index = point.x + point.y * origin.query.width
return fetch({
url: `${origin.url}/${index}_paths.dat`
})
}
function getPointForLonLat (position, currentZoom: number, query) {
const pixel = Leaflet.CRS.EPSG3857.latLngToPoint(
lonlat.toLeaflet(position),
currentZoom
)
const scale = Math.pow(2, query.zoom - currentZoom)
let {x, y} = pixel
x = x * scale - query.west | 0
y = y * scale - query.north | 0
return {x, y}
}
const TIMES_GRID_TYPE = 'ACCESSGR'
function parseTimesData (ab: ArrayBuffer) {
const data = new Int32Array(ab)
const headerData = new Int8Array(ab)
const headerType = String.fromCharCode(...headerData.slice(0, TIMES_GRID_TYPE.length))
if (headerType !== TIMES_GRID_TYPE) {
throw new Error(`Retrieved grid header ${headerType} !== ${TIMES_GRID_TYPE}. Please check your data.`)
}
let offset = 2
const version = data[offset++]
const zoom = data[offset++]
const west = data[offset++]
const north = data[offset++]
const width = data[offset++]
const height = data[offset++]
const nSamples = data[offset++]
return {
version,
zoom,
west,
north,
width,
height,
nSamples,
data: data.slice(TIMES_GRID_TYPE.length)
}
}
/**
*
*/
const PATHS_GRID_TYPE = 'PATHGRID'
function parsePathsData (ab: ArrayBuffer) {
const data = new Int32Array(ab.slice(PATHS_GRID_TYPE.length))
const headerData = new Int8Array(ab)
let offset = PATHS_GRID_TYPE.length
const headerType = String.fromCharCode(...headerData.slice(0, offset))
if (headerType !== PATHS_GRID_TYPE) {
throw new Error(`Retrieved grid header ${headerType} !== ${PATHS_GRID_TYPE}. Please check your data.`)
}
const next = () => data[offset++]
const nDestinations = next()
const nIterations = next()
const nPathLists = next()
const pathLists = []
for (let i = 0; i < nPathLists; i++) {
const nPaths = next()
const pathList = []
for (let j = 0; j < nPaths; j++) {
pathList.push([next(), next(), next()]) // boardStopId, patternId, alightStopId
}
pathLists.push(pathList)
}
const targets = []
for (let i = 0; i < nDestinations; i++) {
const pathIndexes = []
let previousValue = 0
for (let j = 0; j < nIterations; j++) {
const delta = next()
const pathIndex = delta + previousValue
pathIndexes.push(pathIndex)
previousValue = pathIndex
}
targets.push(pathIndexes)
}
return {pathLists, targets}
}
function validatePathLists (pathLists, td) {
const stopInAllData = (id) => hasStop(id, td.stops)
pathLists.forEach(pathList => {
pathList.forEach(([boardStopId, patternId, alightStopId]) => {
const pattern = td.patterns.find(p => p.pattern_id === `${patternId}`)
if (!pattern) {
console.error(`Pattern ${patternId} not in transitive data.`)
}
const stopInPattern = (id) => hasStop(id, pattern.stops)
if (!stopInPattern(boardStopId)) {
console.error(`Board stop ${boardStopId} not found in pattern`)
if (!stopInAllData(boardStopId)) {
console.error(`Board stop ${boardStopId} not found in all data`)
}
}
if (!stopInPattern(alightStopId)) {
console.error(`Alight stop ${alightStopId} not found in pattern`)
if (!stopInAllData(alightStopId)) {
console.error(`Alight stop ${alightStopId} not found in all data`)
}
}
})
})
}
const hasStop = (stopId, stops) => !!stops.find(s => s.stop_id === `${stopId}`)