tracktracker/back/data.js

185 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const requestp = require('request-promise-native');
const {makeCached} = require('./util');
const OVERPASS_ENDPOINT = 'https://lz4.overpass-api.de/api/interpreter';
const fetchLineData = makeCached(async (lineRef) =>
{
// Retrieve routes, ways and stops from OpenStreetMap
const rawData = await requestp.post(OVERPASS_ENDPOINT, {form: `\
data=[out:json];
// Find the public transport line bearing the requested reference
relation[network="TaM"][type="route_master"][ref="${lineRef}"];
// Recursively fetch routes, ways and stops inside the line
(._; >>;);
out body qt;
`});
const elementsList = JSON.parse(rawData).elements;
// Extract all routes for the given line
const rawRoutes = elementsList.filter(elt =>
elt.type === 'relation'
&& elt.tags.type === 'route'
&& elt.tags.ref === lineRef
);
// If no route is found, assume the line does not exist
if (rawRoutes.length === 0)
{
return null;
}
// Index retrieved objects by their ID
const elements = elementsList.reduce((prev, elt) =>
{
prev[elt.id] = elt;
return prev;
}, {});
const color = rawRoutes[0].tags.colour || '#000000';
// Extract stops for each route of the line
const routes = rawRoutes.map(route => ({
from: route.tags.from,
to: route.tags.to,
// Retrieve each stops information (stop order in the relation is
// assumed to reflect reality)
stops: route.members
.filter(({role}) => role === 'stop')
.map(({ref}) => {
const elt = elements[ref];
return {
id: ref,
lat: elt.lat,
lon: elt.lon,
name: elt.tags.name,
};
}),
// List of ways making up the route (raw)
rawWays: route.members
.filter(({role}) => role === '')
.map(({ref}) => ref)
}));
// Process ways in each route to sort and merge them
for (let route of routes)
{
const {from, to, stops, rawWays} = route;
// Construct a graph with all connected nodes
const nodeNeighbors = new Map();
for (let id of rawWays)
{
const {type, nodes} = elements[id];
if (type === 'way' && nodes.length >= 1)
{
let previousNode = nodes[0];
if (!nodeNeighbors.has(previousNode))
{
nodeNeighbors.set(previousNode, new Set());
}
for (let node of nodes.slice(1))
{
if (!nodeNeighbors.has(node))
{
nodeNeighbors.set(node, new Set([previousNode]));
}
else
{
nodeNeighbors.get(node).add(previousNode);
}
nodeNeighbors.get(previousNode).add(node);
previousNode = node;
}
}
}
// Find way from first stop through the end using DFS
const numberOfStops = stops.length;
const ways = [];
let currentStopIndex = 0;
while (currentStopIndex + 1 < numberOfStops)
{
const currentStop = stops[currentStopIndex];
const nextStop = stops[currentStopIndex + 1];
const visited = new Set();
const stack = [{
currentNode: currentStop.id,
way: [currentStop.id],
}];
let found = false;
while (stack.length !== 0)
{
const {currentNode, way} = stack.pop();
visited.add(currentNode);
if (currentNode === nextStop.id)
{
// Arrived at next stop
ways.push(way);
found = true;
break;
}
const neighbors = nodeNeighbors.get(currentNode) || [];
for (let nextNode of neighbors)
{
if (!visited.has(nextNode))
{
stack.push({
currentNode: nextNode,
way: way.concat([nextNode]),
});
}
}
}
if (!found)
{
throw new Error(`No way between stop “${currentStop.name}\
(${currentStop.id}) and stop “${nextStop.name}” (${nextStop.id}) on line \
${lineRef}s route from “${from}” to “${to}`);
}
++currentStopIndex;
}
// Only keep geo coordinates for each way node
route.ways = ways.map(way =>
way.map(id =>
{
const node = elements[id];
return {lat: node.lat, lon: node.lon};
})
);
delete route.rawWays;
}
return {
ref: lineRef,
color,
routes
};
});
exports.fetchLineData = fetchLineData;