commit 8a05d72528c7f252804f122328613d5c8044cee1 Author: M4x1m3 Date: Tue Oct 29 15:03:50 2024 +0100 [*] Added code diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0e3f51a --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +CTS_TOKEN= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad60b67 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +data/ \ No newline at end of file diff --git a/download_data.py b/download_data.py new file mode 100755 index 0000000..0d1f907 --- /dev/null +++ b/download_data.py @@ -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) \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..7a39d43 --- /dev/null +++ b/index.html @@ -0,0 +1,39 @@ + + + + + + Test + + + + +
+ + + + + + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..17bded4 --- /dev/null +++ b/main.js @@ -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: '© 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 = ` + + + + ` + 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 = ` + + + + ` + 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); + +//