Initial commit
This commit is contained in:
commit
16f7216b54
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
|
@ -0,0 +1,184 @@
|
||||||
|
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 stop’s 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;
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 696 B |
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 618 B |
|
@ -0,0 +1,30 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>TaM</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="leaflet.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body, html
|
||||||
|
{
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
|
||||||
|
<script src="leaflet.js"></script>
|
||||||
|
<script src="index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,59 @@
|
||||||
|
// MAP
|
||||||
|
const map = L.map('map').setView([43.610, 3.8612], 21);
|
||||||
|
|
||||||
|
L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
|
||||||
|
attribution: [
|
||||||
|
// TODO: Attribution des données TaM
|
||||||
|
`Données cartographiques © Contributeurs d’<a href="https://www.open\
|
||||||
|
streetmap.org/">OpenStreetMap</a>, <a href="https://creativecommons.org/\
|
||||||
|
licenses/by-sa/2.0/">CC-BY-SA</a>`,
|
||||||
|
'Imagerie © <a href="https://www.mapbox.com/">Mapbox</a>',
|
||||||
|
].join(' | '),
|
||||||
|
maxZoom: 18,
|
||||||
|
tileSize: 512,
|
||||||
|
zoomOffset: -1,
|
||||||
|
id: 'mapbox/streets-v11',
|
||||||
|
accessToken: 'pk.eyJ1IjoibWF0dGVvZGVsYWJyZSIsImEiOiJjazUxaWNsdXcwdWhjM29tc2xndXJoNGtxIn0.xELwMerqJLFimIqU6RxnZw'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// </MAP>
|
||||||
|
//
|
||||||
|
// TAM
|
||||||
|
|
||||||
|
|
||||||
|
const SERVER = 'http://localhost:3000';
|
||||||
|
|
||||||
|
fetch(SERVER + '/line/6').then(res => res.json()).then(line =>
|
||||||
|
{
|
||||||
|
const color = line.color;
|
||||||
|
|
||||||
|
line.routes.forEach(route =>
|
||||||
|
{
|
||||||
|
route.ways.forEach(way =>
|
||||||
|
{
|
||||||
|
const wayPoints = way.map(({lat, lon}) => [lat, lon]);
|
||||||
|
|
||||||
|
L.polyline(wayPoints, {weight: 8, color: '#888'}).addTo(map);
|
||||||
|
L.polyline(wayPoints, {weight: 6, color}).addTo(map);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
line.routes.forEach(route =>
|
||||||
|
{
|
||||||
|
route.stops.forEach(stop =>
|
||||||
|
{
|
||||||
|
const stopMarker = L.circleMarker(
|
||||||
|
[stop.lat, stop.lon],
|
||||||
|
{
|
||||||
|
fillColor: color,
|
||||||
|
radius: 6,
|
||||||
|
fillOpacity: 1,
|
||||||
|
color: '#888',
|
||||||
|
weight: 2
|
||||||
|
}
|
||||||
|
).addTo(map);
|
||||||
|
|
||||||
|
stopMarker.bindPopup(stop.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,640 @@
|
||||||
|
/* required styles */
|
||||||
|
|
||||||
|
.leaflet-pane,
|
||||||
|
.leaflet-tile,
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow,
|
||||||
|
.leaflet-tile-container,
|
||||||
|
.leaflet-pane > svg,
|
||||||
|
.leaflet-pane > canvas,
|
||||||
|
.leaflet-zoom-box,
|
||||||
|
.leaflet-image-layer,
|
||||||
|
.leaflet-layer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.leaflet-container {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.leaflet-tile,
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
/* Prevents IE11 from highlighting tiles in blue */
|
||||||
|
.leaflet-tile::selection {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||||
|
.leaflet-safari .leaflet-tile {
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
}
|
||||||
|
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||||
|
.leaflet-safari .leaflet-tile-container {
|
||||||
|
width: 1600px;
|
||||||
|
height: 1600px;
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||||
|
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||||
|
.leaflet-container .leaflet-overlay-pane svg,
|
||||||
|
.leaflet-container .leaflet-marker-pane img,
|
||||||
|
.leaflet-container .leaflet-shadow-pane img,
|
||||||
|
.leaflet-container .leaflet-tile-pane img,
|
||||||
|
.leaflet-container img.leaflet-image-layer,
|
||||||
|
.leaflet-container .leaflet-tile {
|
||||||
|
max-width: none !important;
|
||||||
|
max-height: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-container.leaflet-touch-zoom {
|
||||||
|
-ms-touch-action: pan-x pan-y;
|
||||||
|
touch-action: pan-x pan-y;
|
||||||
|
}
|
||||||
|
.leaflet-container.leaflet-touch-drag {
|
||||||
|
-ms-touch-action: pinch-zoom;
|
||||||
|
/* Fallback for FF which doesn't support pinch-zoom */
|
||||||
|
touch-action: none;
|
||||||
|
touch-action: pinch-zoom;
|
||||||
|
}
|
||||||
|
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||||
|
-ms-touch-action: none;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
.leaflet-container {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.leaflet-container a {
|
||||||
|
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||||
|
}
|
||||||
|
.leaflet-tile {
|
||||||
|
filter: inherit;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.leaflet-tile-loaded {
|
||||||
|
visibility: inherit;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-box {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 800;
|
||||||
|
}
|
||||||
|
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||||
|
.leaflet-overlay-pane svg {
|
||||||
|
-moz-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-pane { z-index: 400; }
|
||||||
|
|
||||||
|
.leaflet-tile-pane { z-index: 200; }
|
||||||
|
.leaflet-overlay-pane { z-index: 400; }
|
||||||
|
.leaflet-shadow-pane { z-index: 500; }
|
||||||
|
.leaflet-marker-pane { z-index: 600; }
|
||||||
|
.leaflet-tooltip-pane { z-index: 650; }
|
||||||
|
.leaflet-popup-pane { z-index: 700; }
|
||||||
|
|
||||||
|
.leaflet-map-pane canvas { z-index: 100; }
|
||||||
|
.leaflet-map-pane svg { z-index: 200; }
|
||||||
|
|
||||||
|
.leaflet-vml-shape {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
.lvml {
|
||||||
|
behavior: url(#default#VML);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* control positioning */
|
||||||
|
|
||||||
|
.leaflet-control {
|
||||||
|
position: relative;
|
||||||
|
z-index: 800;
|
||||||
|
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.leaflet-top,
|
||||||
|
.leaflet-bottom {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.leaflet-top {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.leaflet-right {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.leaflet-bottom {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.leaflet-left {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.leaflet-control {
|
||||||
|
float: left;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.leaflet-right .leaflet-control {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.leaflet-top .leaflet-control {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-bottom .leaflet-control {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-left .leaflet-control {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-right .leaflet-control {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* zoom and fade animations */
|
||||||
|
|
||||||
|
.leaflet-fade-anim .leaflet-tile {
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
.leaflet-fade-anim .leaflet-popup {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition: opacity 0.2s linear;
|
||||||
|
-moz-transition: opacity 0.2s linear;
|
||||||
|
transition: opacity 0.2s linear;
|
||||||
|
}
|
||||||
|
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-animated {
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
-ms-transform-origin: 0 0;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||||
|
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
}
|
||||||
|
.leaflet-zoom-anim .leaflet-tile,
|
||||||
|
.leaflet-pan-anim .leaflet-tile {
|
||||||
|
-webkit-transition: none;
|
||||||
|
-moz-transition: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* cursors */
|
||||||
|
|
||||||
|
.leaflet-interactive {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.leaflet-grab {
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
.leaflet-crosshair,
|
||||||
|
.leaflet-crosshair .leaflet-interactive {
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
.leaflet-popup-pane,
|
||||||
|
.leaflet-control {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
.leaflet-dragging .leaflet-grab,
|
||||||
|
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||||
|
.leaflet-dragging .leaflet-marker-draggable {
|
||||||
|
cursor: move;
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
cursor: -moz-grabbing;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* marker & overlays interactivity */
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow,
|
||||||
|
.leaflet-image-layer,
|
||||||
|
.leaflet-pane > svg path,
|
||||||
|
.leaflet-tile-container {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-marker-icon.leaflet-interactive,
|
||||||
|
.leaflet-image-layer.leaflet-interactive,
|
||||||
|
.leaflet-pane > svg path.leaflet-interactive,
|
||||||
|
svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
|
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* visual tweaks */
|
||||||
|
|
||||||
|
.leaflet-container {
|
||||||
|
background: #ddd;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
.leaflet-container a {
|
||||||
|
color: #0078A8;
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-active {
|
||||||
|
outline: 2px solid orange;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-box {
|
||||||
|
border: 2px dotted #38f;
|
||||||
|
background: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* general typography */
|
||||||
|
.leaflet-container {
|
||||||
|
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* general toolbar styles */
|
||||||
|
|
||||||
|
.leaflet-bar {
|
||||||
|
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.leaflet-bar a,
|
||||||
|
.leaflet-bar a:hover {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.leaflet-bar a,
|
||||||
|
.leaflet-control-layers-toggle {
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:hover {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:first-child {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:last-child {
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.leaflet-bar a.leaflet-disabled {
|
||||||
|
cursor: default;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-bar a {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-bar a:first-child {
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-bar a:last-child {
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* zoom control */
|
||||||
|
|
||||||
|
.leaflet-control-zoom-in,
|
||||||
|
.leaflet-control-zoom-out {
|
||||||
|
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||||
|
text-indent: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* layers control */
|
||||||
|
|
||||||
|
.leaflet-control-layers {
|
||||||
|
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-toggle {
|
||||||
|
background-image: url(images/layers.png);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
.leaflet-retina .leaflet-control-layers-toggle {
|
||||||
|
background-image: url(images/layers-2x.png);
|
||||||
|
background-size: 26px 26px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-control-layers-toggle {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers .leaflet-control-layers-list,
|
||||||
|
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-expanded {
|
||||||
|
padding: 6px 10px 6px 6px;
|
||||||
|
color: #333;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-scrollbar {
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-selector {
|
||||||
|
margin-top: 2px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-separator {
|
||||||
|
height: 0;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
margin: 5px -10px 5px -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default icon URLs */
|
||||||
|
.leaflet-default-icon-path {
|
||||||
|
background-image: url(images/marker-icon.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* attribution and scale controls */
|
||||||
|
|
||||||
|
.leaflet-container .leaflet-control-attribution {
|
||||||
|
background: #fff;
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution,
|
||||||
|
.leaflet-control-scale-line {
|
||||||
|
padding: 0 5px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.leaflet-container .leaflet-control-attribution,
|
||||||
|
.leaflet-container .leaflet-control-scale {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.leaflet-left .leaflet-control-scale {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-bottom .leaflet-control-scale {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line {
|
||||||
|
border: 2px solid #777;
|
||||||
|
border-top: none;
|
||||||
|
line-height: 1.1;
|
||||||
|
padding: 2px 5px 1px;
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line:not(:first-child) {
|
||||||
|
border-top: 2px solid #777;
|
||||||
|
border-bottom: none;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||||
|
border-bottom: 2px solid #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-control-attribution,
|
||||||
|
.leaflet-touch .leaflet-control-layers,
|
||||||
|
.leaflet-touch .leaflet-bar {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-control-layers,
|
||||||
|
.leaflet-touch .leaflet-bar {
|
||||||
|
border: 2px solid rgba(0,0,0,0.2);
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* popup */
|
||||||
|
|
||||||
|
.leaflet-popup {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content-wrapper {
|
||||||
|
padding: 1px;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content {
|
||||||
|
margin: 13px 19px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content p {
|
||||||
|
margin: 18px 0;
|
||||||
|
}
|
||||||
|
.leaflet-popup-tip-container {
|
||||||
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -20px;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.leaflet-popup-tip {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
margin: -10px auto 0;
|
||||||
|
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-moz-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
.leaflet-popup-content-wrapper,
|
||||||
|
.leaflet-popup-tip {
|
||||||
|
background: white;
|
||||||
|
color: #333;
|
||||||
|
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-popup-close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 4px 4px 0 0;
|
||||||
|
border: none;
|
||||||
|
text-align: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 14px;
|
||||||
|
font: 16px/14px Tahoma, Verdana, sans-serif;
|
||||||
|
color: #c3c3c3;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-popup-close-button:hover {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.leaflet-popup-scrolled {
|
||||||
|
overflow: auto;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||||
|
zoom: 1;
|
||||||
|
}
|
||||||
|
.leaflet-oldie .leaflet-popup-tip {
|
||||||
|
width: 24px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||||
|
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||||
|
}
|
||||||
|
.leaflet-oldie .leaflet-popup-tip-container {
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-oldie .leaflet-control-zoom,
|
||||||
|
.leaflet-oldie .leaflet-control-layers,
|
||||||
|
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||||
|
.leaflet-oldie .leaflet-popup-tip {
|
||||||
|
border: 1px solid #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* div icon */
|
||||||
|
|
||||||
|
.leaflet-div-icon {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Tooltip */
|
||||||
|
/* Base styles for the element that has a tooltip */
|
||||||
|
.leaflet-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
padding: 6px;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #222;
|
||||||
|
white-space: nowrap;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.leaflet-tooltip.leaflet-clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-top:before,
|
||||||
|
.leaflet-tooltip-bottom:before,
|
||||||
|
.leaflet-tooltip-left:before,
|
||||||
|
.leaflet-tooltip-right:before {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
border: 6px solid transparent;
|
||||||
|
background: transparent;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Directions */
|
||||||
|
|
||||||
|
.leaflet-tooltip-bottom {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-top {
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-bottom:before,
|
||||||
|
.leaflet-tooltip-top:before {
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-top:before {
|
||||||
|
bottom: 0;
|
||||||
|
margin-bottom: -12px;
|
||||||
|
border-top-color: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-bottom:before {
|
||||||
|
top: 0;
|
||||||
|
margin-top: -12px;
|
||||||
|
margin-left: -6px;
|
||||||
|
border-bottom-color: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-left {
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-right {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-left:before,
|
||||||
|
.leaflet-tooltip-right:before {
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-left:before {
|
||||||
|
right: 0;
|
||||||
|
margin-right: -12px;
|
||||||
|
border-left-color: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-right:before {
|
||||||
|
left: 0;
|
||||||
|
margin-left: -12px;
|
||||||
|
border-right-color: #fff;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "tamview",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"csv-parse": "^4.8.3",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"leaflet": "^1.6.0",
|
||||||
|
"parcel-bundler": "^1.12.4",
|
||||||
|
"request": "^2.88.0",
|
||||||
|
"request-promise-native": "^1.0.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^2.0.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
const express = require('express');
|
||||||
|
const request = require('request');
|
||||||
|
const csv = require('csv-parse');
|
||||||
|
|
||||||
|
const data = require('./data');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = 3000;
|
||||||
|
|
||||||
|
app.use(express.static('front'));
|
||||||
|
|
||||||
|
const TAM_REALTIME = 'http://data.montpellier3m.fr/node/10732/download';
|
||||||
|
|
||||||
|
app.get('/realtime', (req, res) =>
|
||||||
|
{
|
||||||
|
const parser = csv({
|
||||||
|
delimiter: ';',
|
||||||
|
});
|
||||||
|
|
||||||
|
passages = {};
|
||||||
|
passagesLast = Date.now();
|
||||||
|
|
||||||
|
const stream = request(TAM_ENDPOINT)
|
||||||
|
.pipe(parser);
|
||||||
|
|
||||||
|
stream.on('readable', () =>
|
||||||
|
{
|
||||||
|
let row;
|
||||||
|
|
||||||
|
while (row = stream.read())
|
||||||
|
{
|
||||||
|
if (row.length === 0 || row[0] === 'course')
|
||||||
|
{
|
||||||
|
// Ignore les lignes invalides
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passages[row[2]] === undefined)
|
||||||
|
{
|
||||||
|
passages[row[2]] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
passages[row[2]].push({
|
||||||
|
ligne: row[4],
|
||||||
|
destination: row[5],
|
||||||
|
direction: row[6],
|
||||||
|
time: row[7],
|
||||||
|
theorique: row[8],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', () => res());
|
||||||
|
stream.on('error', err => rej(err));
|
||||||
|
|
||||||
|
res.send('Hello World!');
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('/line/:lineRef', async (req, res) =>
|
||||||
|
{
|
||||||
|
const lineData = await data.fetchLineData(req.params.lineRef);
|
||||||
|
res.json(lineData);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* Wrap an async function to cache its result.
|
||||||
|
*
|
||||||
|
* On first call of the wrapped function, the result will be stored and further
|
||||||
|
* calls will directly return this cached value (always inside a promise).
|
||||||
|
*
|
||||||
|
* A `.noCache` method is provided to force getting a fresh value.
|
||||||
|
*
|
||||||
|
* Each cache value is scoped to the array of arguments that yielded it
|
||||||
|
* (comparison done using its JSON representation).
|
||||||
|
*
|
||||||
|
* @param fun Function to wrap.
|
||||||
|
* @return Wrapped function.
|
||||||
|
*/
|
||||||
|
const makeCached = fun =>
|
||||||
|
{
|
||||||
|
const cachedResults = new Map();
|
||||||
|
|
||||||
|
const noCache = async (...args) =>
|
||||||
|
{
|
||||||
|
const result = await fun(...args);
|
||||||
|
cachedResults.set(JSON.stringify(args), result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const withCache = async (...args) =>
|
||||||
|
{
|
||||||
|
const key = JSON.stringify(args);
|
||||||
|
|
||||||
|
if (cachedResults.has(key))
|
||||||
|
{
|
||||||
|
return cachedResults.get(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const result = await fun(...args);
|
||||||
|
cachedResults.set(key, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
withCache.noCache = noCache;
|
||||||
|
return withCache;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.makeCached = makeCached;
|
Loading…
Reference in New Issue