[*] Added code

This commit is contained in:
Maxime FRIESS 2024-10-29 15:03:50 +01:00
commit 8a05d72528
Signed by: Maxime FRIESS
GPG key ID: 5294B3B26FA19584
5 changed files with 327 additions and 0 deletions

1
.env.example Normal file
View file

@ -0,0 +1 @@
CTS_TOKEN=

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.env
data/

46
download_data.py Executable file
View file

@ -0,0 +1,46 @@
#!/bin/env python3
from dotenv import load_dotenv
import requests
import os
import json
import pathlib
import requests.auth
TO_DOWNLOAD = [
('https://data.strasbourg.eu/api/explore/v2.1/catalog/datasets/lignes_tram/records', 'lines_tram.json', 'LINES_TRAM'),
('https://data.strasbourg.eu/api/explore/v2.1/catalog/datasets/lignes_de_bus/records?refine=type_ligne%3A%22BHNS%22 ', 'lines_bhns.json', 'LINES_BHNS'),
]
TO_DOWNLOAD_CTS = [
('https://api.cts-strasbourg.eu/v1/siri/2.0/stoppoints-discovery?includeLinesDestinations=true', 'stoppoints.json', 'STOPPOINTS'),
('https://api.cts-strasbourg.eu/v1/siri/2.0/lines-discovery', 'lines.json', 'LINES'),
('https://api.cts-strasbourg.eu/v1/siri/2.0/estimated-timetable?LineRef=A&LineRef=B&LineRef=C&LineRef=D&LineRef=E&LineRef=F&GetStopIdInsteadOfStopCode=true', 'timetable.json', 'TIMETABLE')
]
load_dotenv()
CTS_TOKEN = os.getenv('CTS_TOKEN')
OUT = "data"
js_data = ""
os.makedirs(pathlib.Path(OUT), exist_ok=True)
for url, file, var in TO_DOWNLOAD:
rep = requests.get(url)
d = rep.json()
with open(pathlib.Path(OUT) / file, 'w') as f:
json.dump(d, f, indent=2)
js_data += 'const ' + var + ' = ' + json.dumps(d) + '\n'
for url, file, var in TO_DOWNLOAD_CTS:
rep = requests.get(url, auth=requests.auth.HTTPBasicAuth(CTS_TOKEN, ''))
d = rep.json()
with open(pathlib.Path(OUT) / file, 'w') as f:
json.dump(d, f, indent=2)
js_data += 'const ' + var + ' = ' + json.dumps(d) + '\n'
with open(pathlib.Path(OUT) / "data.js", 'w') as f:
f.write(js_data)

39
index.html Normal file
View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test</title>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
<style>
html,
body {
padding: 0;
margin: 0;
}
#map {
width: 100vw;
height: 100vh;
background-color: #090909;
}
</style>
</head>
<body>
<div id="map"></div>
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""
></script>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js"></script>
<script src="data/data.js"></script>
<script src="main.js"></script>
</body>
</html>

239
main.js Normal file
View file

@ -0,0 +1,239 @@
// LINES_TRAM
// LINES_BHNS
// LINES
// STOPPOINTS
const LINES_OVERRIDE = {
'Place d\'Islande': ['F'],
'Gare Centrale': ['C', 'A'],
'Homme de Fer': ['C', 'A']
}
const map = L.map('map');
// https://cartodb-basemaps-a.global.ssl.fastly.net/dark_nolabels/{z}/{x}/{y}.png
// https://tile.openstreetmap.org/{z}/{x}/{y}.png
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; carto.com'
}).addTo(map);
const lignes = {};
// Dessigner les lignes de tram
LINES_TRAM.results.forEach(line => {
// Récupération des points de la ligne
const points = line.geo_shape.geometry.coordinates;
// Récupération de la couleur
const color = LINES.LinesDelivery.AnnotatedLineRef.filter(d => d.LineRef === line.ligne)[0].Extension.RouteColor;
// Ligne extérieur
const polyline = L.polyline(L.GeoJSON.coordsToLatLngs(points), {
color: '#' + color,
weight: 6,
}).addTo(map);
// Ligne blanche centrale
const polyline_white = L.polyline(L.GeoJSON.coordsToLatLngs(points), {
color: '#ffffff',
weight: 2,
opacity: 0.5
}).addTo(map);
// Stockage des points pour traitement ultérieur
lignes[line.ligne] = points;
});
const stops = {}
const stations = {}
// Pre-parsing des arrêts pour dessin
// (en gros on AVG les coordéonnes)
STOPPOINTS.StopPointsDelivery.AnnotatedStopPointRef.filter(stop => stop.Lines.filter(line => line.Extension.RouteType === "tram").length > 0).forEach(stop => {
stations[stop.StopPointRef] = [
stop.Location.Longitude,
stop.Location.Latitude
];
if (!(stop.StopName in stops)) {
stops[stop.StopName] = {
name: stop.StopName,
lines: new Set(stop.Lines.map(l => l.LineRef)),
lat: stop.Location.Latitude,
lng: stop.Location.Longitude,
cnt: 1
};
} else {
stops[stop.StopName].lat += stop.Location.Latitude;
stops[stop.StopName].lng += stop.Location.Longitude;
stops[stop.StopName].cnt += 1;
stop.Lines.map(l => l.LineRef).forEach(stops[stop.StopName].lines.add, stops[stop.StopName].lines);
}
});
/// Dessin des coordonnées
for (const name in stops) {
const stop = stops[name];
let line_name;
// On trouve sur quel ligne l'arrêt se trouve, pour snap sur la ligne
if (name in LINES_OVERRIDE) {
line_name = LINES_OVERRIDE[name]
} else {
for (const line of stop.lines) {
if (line in lignes) {
line_name = [line];
}
}
}
stop.lat /= stop.cnt;
stop.lng /= stop.cnt;
// On place l'arrêt
if (line_name !== undefined) {
for(the_name of line_name) {
const tramline = turf.lineString(lignes[the_name]);
const pt = turf.point([stop.lng, stop.lat]);
// Snap l'arrêt sur la ligne
const snapped = turf.nearestPointOnLine(tramline, pt);
const marker = L.circleMarker(L.latLng(snapped.geometry.coordinates[1], snapped.geometry.coordinates[0]), {
radius: 6,
color: "#1d1d1d",
fillColor: "#ffffff",
weight: 3,
fillOpacity: 1
}).addTo(map);
marker.bindPopup(stop.name);
}
}
}
// Trams en cours de circulation
let trams = {};
// Cache des segments de lignes
// (parce que c'est lour à calculer donc autant le faire qu'une fois pôour chaque segment)
const segments_cache = {}
// Mise à jour périodique
setInterval(() => {
const current_date = new Date();
const current_trams = new Set();
// On parcours la delivery
for (const delivery of TIMETABLE.ServiceDelivery.EstimatedTimetableDelivery) {
const cycle_name = delivery.ShortestPossibleCycle;
// Les timeframes
for (const frame of delivery.EstimatedJourneyVersionFrame) {
// Et les journeys
for (const journey of frame.EstimatedVehicleJourney) {
let previous = undefined;
const tram_unique_id = cycle_name + ";;" + journey.LineRef + ";;" + journey.FramedVehicleJourneyRef.DatedVehicleJourneySAERef
// console.log(tram_unique_id)
// On parcours les différentes stations sur le chemin
for (const call of journey.EstimatedCalls) {
if (previous === undefined) {
previous = call;
continue;
}
// On récupère l'heure de départ de la stations précédente, l'heure d'arrivée à la stations actuelle
const previous_dep = new Date(previous.ExpectedDepartureTime);
const call_arrival = new Date(call.ExpectedArrivalTime);
const call_departure = new Date(call.ExpectedDepartureTime);
// Si l'heure actuelle est entre le départ de la station précédente et l'arrivée
// de la station actuelle on sait que le tram est sur ce tronçon
if (previous_dep.getTime() <= current_date.getTime() && current_date.getTime() <= call_arrival.getTime()) {
// On calcul le pourcentage d'avancement sur le tronçon
// (interpolation linéaire à la con)
const percent = (current_date.getTime() - previous_dep.getTime()) / (call_arrival.getTime() - previous_dep.getTime());
// On place le tram
if (previous.StopPointRef in stations && call.StopPointRef in stations && journey.LineRef in lignes) {
let seg;
const segments_cache_id = journey.LineRef + ';;' + previous.StopPointRef + ';;' + call.StopPointRef;
if (segments_cache_id in segments_cache) {
seg = segments_cache[segments_cache_id];
} else {
seg = turf.lineSlice(stations[previous.StopPointRef], stations[call.StopPointRef], turf.lineString(lignes[journey.LineRef]));
segments_cache[segments_cache_id] = seg;
}
const len = turf.length(seg);
const position = turf.along(seg, len * percent);
if (tram_unique_id in trams) {
const last_position = map.project(trams[tram_unique_id].getLatLng(), 5)
const current_position = map.project(L.latLng(position.geometry.coordinates[1], position.geometry.coordinates[0]), 5)
const x = current_position.x - last_position.x;
const y = current_position.y - last_position.y;
const angle = Math.atan2(y, x);
const elem = document.getElementById(`tram;;${tram_unique_id}`)
if (elem !== null) {
elem.style.transform = `rotate(${angle}rad)`;
}
trams[tram_unique_id].setLatLng(L.latLng(position.geometry.coordinates[1], position.geometry.coordinates[0]));
} else {
const color = LINES.LinesDelivery.AnnotatedLineRef.filter(d => d.LineRef === journey.LineRef)[0].Extension.RouteColor;
/*const marker = L.circleMarker(L.latLng(position.geometry.coordinates[1], position.geometry.coordinates[0]), {
radius: 4,
color: "#1d1d1d",
fillColor: "#" + color,
weight: 2,
fillOpacity: 1
}).addTo(map);*/
const content = `
<svg viewBox="-10 -10 20 20" width="15" height="15" xmlns="http://www.w3.org/2000/svg" id="tram;;${tram_unique_id}">
<polygon points="-7.5, 5 2.5, 5 7.5, 0 2.5, -5 -7.5, -5" fill="#${color}" stroke="#1d1d1b" stroke-width="2" class="tram"></polygon>
</svg>
`
const myIcon = L.divIcon({className: 'tram-icon', html: content});
const marker = L.marker(L.latLng(position.geometry.coordinates[1], position.geometry.coordinates[0]), {icon: myIcon}).addTo(map);
marker.bindPopup(tram_unique_id);
trams[tram_unique_id] = marker;
console.log("Added " + tram_unique_id);
}
current_trams.add(tram_unique_id);
}
// Si l'heure actuelle et entre l'heure d'arrivée et de départ à une station on place le tram sur cette station.
} else if (call_arrival.getTime() <= current_date.getTime() && current_date.getTime() <= call_departure.getTime()) {
if (tram_unique_id in trams) {
trams[tram_unique_id].setLatLng(L.latLng([stations[call.StopPointRef][1], stations[call.StopPointRef][0]]));
} else {
const color = LINES.LinesDelivery.AnnotatedLineRef.filter(d => d.LineRef === journey.LineRef)[0].Extension.RouteColor;
const content = `
<svg viewBox="-10 -10 20 20" width="15" height="15" xmlns="http://www.w3.org/2000/svg" id="tram;;${tram_unique_id}">
<polygon points="-7.5, 5 2.5, 5 7.5, 0 2.5, -5 -7.5, -5" fill="#${color}" stroke="#1d1d1b" stroke-width="2" class="tram"></polygon>
</svg>
`
const myIcon = L.divIcon({className: 'tram-icon', html: content});
const marker = L.marker(L.latLng([stations[call.StopPointRef][1], stations[call.StopPointRef][0]]), {icon: myIcon}).addTo(map);
marker.bindPopup(tram_unique_id);
trams[tram_unique_id] = marker;
console.log("Added " + tram_unique_id);
}
current_trams.add(tram_unique_id);
}
previous = call;
}
}
}
}
// On vire les anciens trams
for(const key of Object.keys(trams)) {
if (!current_trams.has(key)) {
console.log("Removed " + key);
trams[key].remove();
delete trams[key];
}
}
}, 200);
var latLngBounds = L.latLngBounds([[48.52343058579338, 7.681799380771575], [48.62985324979267, 7.820267901766859]]);
map.fitBounds(latLngBounds);
//