Use PTv2 standard instead of DFS for finding segments
This commit is contained in:
		
							parent
							
								
									783f29ce68
								
							
						
					
					
						commit
						dd008d7ee2
					
				|  | @ -124,6 +124,17 @@ const matchStopNames = (fullName, abbrName) => | ||||||
|     ).length; |     ).length; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Determine if an OSM way is oneway or not. | ||||||
|  |  * | ||||||
|  |  * @param tags Set of tags of the way. | ||||||
|  |  * @return True iff. the way is oneway. | ||||||
|  |  */ | ||||||
|  | const isOneWay = tags => | ||||||
|  |     ('oneway' in tags && tags.oneway === 'yes') | ||||||
|  |     || ('junction' in tags && tags.junction === 'roundabout') | ||||||
|  |     || ('highway' in tags && tags.highway === 'motorway'); | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Fetch stops, segments and lines in the network. |  * Fetch stops, segments and lines in the network. | ||||||
|  * |  * | ||||||
|  | @ -162,13 +173,10 @@ out body qt; | ||||||
|         return prev; |         return prev; | ||||||
|     }, {}); |     }, {}); | ||||||
| 
 | 
 | ||||||
|     // Result object containing all stops
 |     // All stops in the network
 | ||||||
|     const stops = {}; |     const stops = {}; | ||||||
| 
 | 
 | ||||||
|     // Result object containing all segments between stops
 |     // All transport lines of the network
 | ||||||
|     const segments = {}; |  | ||||||
| 
 |  | ||||||
|     // Result object containing all lines
 |  | ||||||
|     const lines = {}; |     const lines = {}; | ||||||
| 
 | 
 | ||||||
|     for (let routeMaster of routeMasters) |     for (let routeMaster of routeMasters) | ||||||
|  | @ -234,7 +242,7 @@ URI: ${osmViewNode}/${stop.id} | ||||||
| 
 | 
 | ||||||
|                         if (candidates.length === 0) |                         if (candidates.length === 0) | ||||||
|                         { |                         { | ||||||
|                             console.warn('No candidate found in TaM data.'); |                             console.warn('No candidate found in TaM data.'); | ||||||
|                         } |                         } | ||||||
|                         else |                         else | ||||||
|                         { |                         { | ||||||
|  | @ -272,171 +280,142 @@ ${joinSentence(Array.from(candidate.directions), ', ', ' or ')} | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Add missing segments between stops
 |         // Reconstruct the line’s route from stop to stop
 | ||||||
|  |         const routes = []; | ||||||
|  | 
 | ||||||
|         for (let route of rawRoutes) |         for (let route of rawRoutes) | ||||||
|         { |         { | ||||||
|             const {from, to} = route.tags; |             const {from, to} = route.tags; | ||||||
| 
 | 
 | ||||||
|             const stops = route.members |             // Human-readable description of the route for errors
 | ||||||
|                 .filter(({role}) => role === 'stop') |             const routeDescription = `line ${lineRef}’s route from \
 | ||||||
|                 .map(({ref}) => elements[ref]) | “${route.tags.from}” to “${route.tags.to}”`;
 | ||||||
|                 .filter(stop => 'ref' in stop.tags) |  | ||||||
|                 .map(stop => ({ |  | ||||||
|                     id: stop.id, |  | ||||||
|                     lat: stop.lat, |  | ||||||
|                     lon: stop.lon, |  | ||||||
|                     ref: stop.tags.ref, |  | ||||||
|                     name: stop.tags.name, |  | ||||||
|                 })); |  | ||||||
| 
 | 
 | ||||||
|             const ways = route.members |             // Check that the route consists of a block of stops and platforms
 | ||||||
|                 .filter(({role}) => role === '') |             // followed by a block of routes as dictated by PTv2
 | ||||||
|                 .map(({ref}) => ref); |             const relationPivot = route.members.findIndex( | ||||||
|  |                 ({role}) => role === '' | ||||||
|  |             ); | ||||||
| 
 | 
 | ||||||
|             // Construct a graph with all connected nodes
 |             if (!route.members.slice(0, relationPivot).every( | ||||||
|             const nodeNeighbors = new Map(); |                 ({role}) => role === 'stop' || role === 'platform' | ||||||
| 
 |             )) | ||||||
|             for (let id of ways) |  | ||||||
|             { |             { | ||||||
|                 const {type, nodes, tags} = elements[id]; |                 throw new Error(`Members with invalid roles in between stops
 | ||||||
| 
 | of ${routeDescription}`);
 | ||||||
|                 const isOneWay = ( |  | ||||||
|                     tags.oneway === 'yes' |  | ||||||
|                     || tags.junction === 'roundabout' |  | ||||||
|                 ); |  | ||||||
| 
 |  | ||||||
|                 const canGoBackward = ( |  | ||||||
|                     !isOneWay |  | ||||||
|                     || parseInt(tags['lanes:psv:backward'], 10) > 0 |  | ||||||
|                 ); |  | ||||||
| 
 |  | ||||||
|                 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()); |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         nodeNeighbors.get(previousNode).add(node); |  | ||||||
| 
 |  | ||||||
|                         if (canGoBackward) |  | ||||||
|                         { |  | ||||||
|                             nodeNeighbors.get(node).add(previousNode); |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         previousNode = node; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Find way from first stop through the end using DFS
 |             if (!route.members.slice(relationPivot).every( | ||||||
|             const numberOfStops = stops.length; |                 ({role}) => role === '' | ||||||
|             let currentStopIndex = 0; |             )) | ||||||
| 
 |  | ||||||
|             while (currentStopIndex + 1 < numberOfStops) |  | ||||||
|             { |             { | ||||||
|                 const currentStop = stops[currentStopIndex]; |                 throw new Error(`Members with invalid roles inside the path
 | ||||||
|                 const nextStop = stops[currentStopIndex + 1]; | of ${routeDescription}`);
 | ||||||
|                 const segmentId = `${currentStop.ref}-${nextStop.ref}`; |             } | ||||||
| 
 | 
 | ||||||
|                 if (!(segmentId in segments)) |             // List of stops in the route, expected to be in the timetable
 | ||||||
|  |             // order as per PTv2 and to be traversed in order by the sequence
 | ||||||
|  |             // of ways extracted below
 | ||||||
|  |             const stops = route.members.slice(0, relationPivot) | ||||||
|  |                 .filter(({role}) => role === 'stop') | ||||||
|  |                 .map(({ref}) => ref); | ||||||
|  | 
 | ||||||
|  |             // List of ways making up the route’s path through its stops
 | ||||||
|  |             // with each way connected to the next through a single point
 | ||||||
|  |             const ways = route.members.slice(relationPivot) | ||||||
|  |                 .map(({ref}) => ref); | ||||||
|  | 
 | ||||||
|  |             // Merge all used ways in a single path
 | ||||||
|  |             let path = []; | ||||||
|  |             let currentNode = stops[0]; | ||||||
|  | 
 | ||||||
|  |             for (let wayIndex = 0; wayIndex < ways.length; wayIndex += 1) | ||||||
|  |             { | ||||||
|  |                 const {nodes: wayNodes, tags: wayTags} | ||||||
|  |                     = elements[ways[wayIndex]]; | ||||||
|  |                 const wayNodesSet = new Set(wayNodes); | ||||||
|  | 
 | ||||||
|  |                 const curNodeIndex = wayNodes.indexOf(currentNode); | ||||||
|  | 
 | ||||||
|  |                 // If not the last way, find a connection point to the next way
 | ||||||
|  |                 // (there should be exactly one)
 | ||||||
|  |                 let nextNode = null; | ||||||
|  |                 let nextNodeIndex = null; | ||||||
|  | 
 | ||||||
|  |                 if (wayIndex + 1 < ways.length) | ||||||
|                 { |                 { | ||||||
|                     const visitedEdges = new Set(); |                     const nextNodeCandidates = elements[ways[wayIndex + 1]] | ||||||
|                     const stack = [{ |                         .nodes.filter(node => wayNodesSet.has(node)); | ||||||
|                         currentNode: currentStop.id, |  | ||||||
|                         segment: [currentStop.id], |  | ||||||
|                     }]; |  | ||||||
| 
 | 
 | ||||||
|                     let found = false; |                     if (nextNodeCandidates.length !== 1) | ||||||
| 
 |  | ||||||
|                     while (stack.length !== 0) |  | ||||||
|                     { |                     { | ||||||
|                         const {currentNode, segment} = stack.pop(); |                         throw new Error(`There should be exactly one point
 | ||||||
| 
 | connecting way n°${wayIndex} and way n°${wayIndex + 1} in ${routeDescription}, | ||||||
|                         if (currentNode === nextStop.id) | but there are ${nextNodeCandidates.length}`);
 | ||||||
|                         { |  | ||||||
|                             // Arrived at next stop
 |  | ||||||
|                             segments[segmentId] = { |  | ||||||
|                                 nodes: segment.map(id => |  | ||||||
|                                 { |  | ||||||
|                                     const {lat, lon} = elements[id]; |  | ||||||
|                                     return {lat, lon}; |  | ||||||
|                                 }), |  | ||||||
|                                 length: geolib.getPathLength(segment.map(id => |  | ||||||
|                                 { |  | ||||||
|                                     const {lat, lon} = elements[id]; |  | ||||||
|                                     return {latitude: lat, longitude: lon}; |  | ||||||
|                                 })), |  | ||||||
|                                 lines: new Set([lineRef]), |  | ||||||
|                             }; |  | ||||||
| 
 |  | ||||||
|                             found = true; |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         const neighbors = nodeNeighbors.get(currentNode) || []; |  | ||||||
| 
 |  | ||||||
|                         for (let nextNode of neighbors) |  | ||||||
|                         { |  | ||||||
|                             const edge = `${currentNode}-${nextNode}`; |  | ||||||
| 
 |  | ||||||
|                             if (!visitedEdges.has(edge)) |  | ||||||
|                             { |  | ||||||
|                                 visitedEdges.add(edge); |  | ||||||
|                                 stack.push({ |  | ||||||
|                                     currentNode: nextNode, |  | ||||||
|                                     segment: segment.concat([nextNode]), |  | ||||||
|                                 }); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     if (!found) |                     nextNode = nextNodeCandidates[0]; | ||||||
|                     { |                     nextNodeIndex = wayNodes.indexOf(nextNode); | ||||||
|                         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}”`);
 |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     segments[segmentId].lines.add(lineRef); |                     nextNodeIndex = wayNodes.length; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 ++currentStopIndex; |                 if (curNodeIndex < nextNodeIndex) | ||||||
|  |                 { | ||||||
|  |                     // Use the way in its normal direction
 | ||||||
|  |                     path = path.concat( | ||||||
|  |                         wayNodes.slice(curNodeIndex, nextNodeIndex) | ||||||
|  |                     ); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     // Use the way in the reverse direction
 | ||||||
|  |                     if (isOneWay(wayTags)) | ||||||
|  |                     { | ||||||
|  |                         throw new Error(`Way n°${wayIndex} in
 | ||||||
|  | ${routeDescription} is one-way and cannot be used in reverse.`);
 | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     path = path.concat( | ||||||
|  |                         wayNodes.slice(nextNodeIndex + 1, curNodeIndex + 1) | ||||||
|  |                                 .reverse() | ||||||
|  |                     ); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 currentNode = nextNode; | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             // Split the path into segments between stops
 | ||||||
|  |             const segments = []; | ||||||
|  | 
 | ||||||
|  |             for (let stopIndex = 0; stopIndex + 1 < stops.length; ++stopIndex) | ||||||
|  |             { | ||||||
|  |                 segments.push(path.slice( | ||||||
|  |                     path.indexOf(stops[stopIndex]), | ||||||
|  |                     path.indexOf(stops[stopIndex + 1] + 1), | ||||||
|  |                 ).map(id => ({ | ||||||
|  |                     lat: elements[id].lat, | ||||||
|  |                     lon: elements[id].lon, | ||||||
|  |                 }))); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             routes.push({ | ||||||
|  |                 from, to, | ||||||
|  |                 name: route.tags.name, | ||||||
|  |                 segments, | ||||||
|  |                 stops: stops.map(id => elements[id].tags.ref), | ||||||
|  |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Construct line objects
 |  | ||||||
|         const routes = rawRoutes.map(route => ({ |  | ||||||
|             from: route.tags.from, |  | ||||||
|             to: route.tags.to, |  | ||||||
| 
 |  | ||||||
|             // Retrieve each stop’s information (stop order in the relation is
 |  | ||||||
|             // assumed to reflect reality)
 |  | ||||||
|             stops: route.members |  | ||||||
|                 .filter(({role}) => role === 'stop') |  | ||||||
|                 .map(({ref}) => elements[ref]) |  | ||||||
|                 .filter(stop => 'ref' in stop.tags) |  | ||||||
|                 .map(stop => stop.tags.ref), |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         lines[lineRef] = { |         lines[lineRef] = { | ||||||
|             color, |             color, | ||||||
|             routes, |             routes, | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return {stops, segments, lines}; |     return {stops, lines}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| exports.fetch = fetch; | exports.fetch = fetch; | ||||||
|  |  | ||||||
							
								
								
									
										291546
									
								
								back/data/network.json
								
								
								
								
							
							
						
						
									
										291546
									
								
								back/data/network.json
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -23,7 +23,7 @@ const fs = require('fs'); | ||||||
| 
 | 
 | ||||||
|                 return value; |                 return value; | ||||||
|             }, |             }, | ||||||
|             '    ' |             4 | ||||||
|         ) |         ) | ||||||
|     ); |     ); | ||||||
| })(); | })(); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue