Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions data/stations.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
{ "id": "TOULON", "label": "Toulon", "city": "Toulon", "lat": 43.1283, "lng": 5.9300, "region": "Provence-Alpes-Côte d'Azur" },
{ "id": "ANGERS SAINT LAUD", "label": "Angers St-Laud", "city": "Angers", "lat": 47.4647, "lng": -0.5566, "region": "Pays de la Loire", "aliases": ["angers"] },
{ "id": "LE MANS", "label": "Le Mans", "city": "Le Mans", "lat": 47.9957, "lng": 0.1929, "region": "Pays de la Loire" },
{ "id": "TOURS", "label": "Tours", "city": "Tours", "lat": 47.3900, "lng": 0.6940, "region": "Centre-Val de Loire", "aliases": ["saint pierre des corps"] },
{ "id": "TOURS", "label": "Tours", "city": "Tours", "lat": 47.3900, "lng": 0.6940, "region": "Centre-Val de Loire" },
{ "id": "POITIERS", "label": "Poitiers", "city": "Poitiers", "lat": 46.5802, "lng": 0.3338, "region": "Nouvelle-Aquitaine" },
{ "id": "ANGOULEME", "label": "Angoulême", "city": "Angoulême", "lat": 45.6519, "lng": 0.1640, "region": "Nouvelle-Aquitaine" },
{ "id": "LA ROCHELLE VILLE", "label": "La Rochelle", "city": "La Rochelle", "lat": 46.1530, "lng": -1.1455, "region": "Nouvelle-Aquitaine", "aliases": ["la rochelle"] },
Expand All @@ -32,7 +32,7 @@
{ "id": "GRENOBLE", "label": "Grenoble", "city": "Grenoble", "lat": 45.1910, "lng": 5.7146, "region": "Auvergne-Rhône-Alpes" },
{ "id": "CHAMBERY CHALLES LES EAUX", "label": "Chambéry", "city": "Chambéry", "lat": 45.5710, "lng": 5.9203, "region": "Auvergne-Rhône-Alpes", "aliases": ["chambery"] },
{ "id": "ANNECY", "label": "Annecy", "city": "Annecy", "lat": 45.9020, "lng": 6.1206, "region": "Auvergne-Rhône-Alpes" },
{ "id": "VALENCE TGV", "label": "Valence TGV", "city": "Valence", "lat": 44.9333, "lng": 4.9783, "region": "Auvergne-Rhône-Alpes", "aliases": ["valence"] },
{ "id": "VALENCE TGV", "label": "Valence TGV", "city": "Valence", "lat": 44.9915, "lng": 4.9787, "region": "Auvergne-Rhône-Alpes", "aliases": ["valence"] },
{ "id": "PERPIGNAN", "label": "Perpignan", "city": "Perpignan", "lat": 42.6970, "lng": 2.8796, "region": "Occitanie" },
{ "id": "BREST", "label": "Brest", "city": "Brest", "lat": 48.3904, "lng": -4.4861, "region": "Bretagne" },
{ "id": "QUIMPER", "label": "Quimper", "city": "Quimper", "lat": 47.9960, "lng": -4.0916, "region": "Bretagne" },
Expand All @@ -43,5 +43,17 @@
{ "id": "BAYONNE", "label": "Bayonne", "city": "Bayonne", "lat": 43.4960, "lng": -1.4730, "region": "Nouvelle-Aquitaine" },
{ "id": "PAU", "label": "Pau", "city": "Pau", "lat": 43.2951, "lng": -0.3708, "region": "Nouvelle-Aquitaine" },
{ "id": "BELFORT MONTBELIARD TGV", "label": "Belfort-Montbéliard TGV", "city": "Belfort", "lat": 47.5862, "lng": 6.8997, "region": "Bourgogne-Franche-Comté" },
{ "id": "LE CREUSOT MONTCEAU MONTCHANIN", "label": "Le Creusot TGV", "city": "Le Creusot", "lat": 46.7903, "lng": 4.4250, "region": "Bourgogne-Franche-Comté" }
{ "id": "LE CREUSOT MONTCEAU MONTCHANIN", "label": "Le Creusot TGV", "city": "Le Creusot", "lat": 46.7653, "lng": 4.4995, "region": "Bourgogne-Franche-Comté" },
{ "id": "LYON ST EXUPERY TGV.", "label": "Lyon St-Exupéry TGV", "city": "Lyon", "lat": 45.7211, "lng": 5.0750, "region": "Auvergne-Rhône-Alpes" },
{ "id": "VALENCE VILLE", "label": "Valence Ville", "city": "Valence", "lat": 44.9280, "lng": 4.8933, "region": "Auvergne-Rhône-Alpes" },
{ "id": "AVIGNON CENTRE", "label": "Avignon Centre", "city": "Avignon", "lat": 43.9419, "lng": 4.8053, "region": "Provence-Alpes-Côte d'Azur" },
{ "id": "LES ARCS DRAGUIGNAN", "label": "Les Arcs-Draguignan", "city": "Draguignan", "lat": 43.4555, "lng": 6.4825, "region": "Provence-Alpes-Côte d'Azur" },
{ "id": "NIMES PONT DU GARD", "label": "Nîmes Pont-du-Gard", "city": "Nîmes", "lat": 43.8168, "lng": 4.5077, "region": "Occitanie" },
{ "id": "MONTPELLIER SUD DE FRANCE", "label": "Montpellier Sud-de-France", "city": "Montpellier", "lat": 43.5948, "lng": 3.9246, "region": "Occitanie" },
{ "id": "BESANCON FRANCHE COMTE TGV", "label": "Besançon Franche-Comté TGV", "city": "Besançon", "lat": 47.3074, "lng": 5.9532, "region": "Bourgogne-Franche-Comté" },
{ "id": "MACON LOCHE TGV", "label": "Mâcon-Loché TGV", "city": "Mâcon", "lat": 46.2829, "lng": 4.7789, "region": "Bourgogne-Franche-Comté" },
{ "id": "CALAIS FRETHUN", "label": "Calais-Fréthun", "city": "Calais", "lat": 50.9012, "lng": 1.8109, "region": "Hauts-de-France" },
{ "id": "ST PIERRE DES CORPS", "label": "St-Pierre-des-Corps", "city": "Tours", "lat": 47.3861, "lng": 0.7236, "region": "Centre-Val de Loire", "aliases": ["saint pierre des corps"] },
{ "id": "MARSEILLE BLANCARDE", "label": "Marseille Blancarde", "city": "Marseille", "lat": 43.2961, "lng": 5.4066, "region": "Provence-Alpes-Côte d'Azur" },
{ "id": "LES AUBRAIS ORLEANS", "label": "Les Aubrais-Orléans", "city": "Orléans", "lat": 47.9268, "lng": 1.9066, "region": "Centre-Val de Loire" }
]
18 changes: 15 additions & 3 deletions public/data/stations.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
{ "id": "TOULON", "label": "Toulon", "city": "Toulon", "lat": 43.1283, "lng": 5.9300, "region": "Provence-Alpes-Côte d'Azur" },
{ "id": "ANGERS SAINT LAUD", "label": "Angers St-Laud", "city": "Angers", "lat": 47.4647, "lng": -0.5566, "region": "Pays de la Loire", "aliases": ["angers"] },
{ "id": "LE MANS", "label": "Le Mans", "city": "Le Mans", "lat": 47.9957, "lng": 0.1929, "region": "Pays de la Loire" },
{ "id": "TOURS", "label": "Tours", "city": "Tours", "lat": 47.3900, "lng": 0.6940, "region": "Centre-Val de Loire", "aliases": ["saint pierre des corps"] },
{ "id": "TOURS", "label": "Tours", "city": "Tours", "lat": 47.3900, "lng": 0.6940, "region": "Centre-Val de Loire" },
{ "id": "POITIERS", "label": "Poitiers", "city": "Poitiers", "lat": 46.5802, "lng": 0.3338, "region": "Nouvelle-Aquitaine" },
{ "id": "ANGOULEME", "label": "Angoulême", "city": "Angoulême", "lat": 45.6519, "lng": 0.1640, "region": "Nouvelle-Aquitaine" },
{ "id": "LA ROCHELLE VILLE", "label": "La Rochelle", "city": "La Rochelle", "lat": 46.1530, "lng": -1.1455, "region": "Nouvelle-Aquitaine", "aliases": ["la rochelle"] },
Expand All @@ -32,7 +32,7 @@
{ "id": "GRENOBLE", "label": "Grenoble", "city": "Grenoble", "lat": 45.1910, "lng": 5.7146, "region": "Auvergne-Rhône-Alpes" },
{ "id": "CHAMBERY CHALLES LES EAUX", "label": "Chambéry", "city": "Chambéry", "lat": 45.5710, "lng": 5.9203, "region": "Auvergne-Rhône-Alpes", "aliases": ["chambery"] },
{ "id": "ANNECY", "label": "Annecy", "city": "Annecy", "lat": 45.9020, "lng": 6.1206, "region": "Auvergne-Rhône-Alpes" },
{ "id": "VALENCE TGV", "label": "Valence TGV", "city": "Valence", "lat": 44.9333, "lng": 4.9783, "region": "Auvergne-Rhône-Alpes", "aliases": ["valence"] },
{ "id": "VALENCE TGV", "label": "Valence TGV", "city": "Valence", "lat": 44.9915, "lng": 4.9787, "region": "Auvergne-Rhône-Alpes", "aliases": ["valence"] },
{ "id": "PERPIGNAN", "label": "Perpignan", "city": "Perpignan", "lat": 42.6970, "lng": 2.8796, "region": "Occitanie" },
{ "id": "BREST", "label": "Brest", "city": "Brest", "lat": 48.3904, "lng": -4.4861, "region": "Bretagne" },
{ "id": "QUIMPER", "label": "Quimper", "city": "Quimper", "lat": 47.9960, "lng": -4.0916, "region": "Bretagne" },
Expand All @@ -43,5 +43,17 @@
{ "id": "BAYONNE", "label": "Bayonne", "city": "Bayonne", "lat": 43.4960, "lng": -1.4730, "region": "Nouvelle-Aquitaine" },
{ "id": "PAU", "label": "Pau", "city": "Pau", "lat": 43.2951, "lng": -0.3708, "region": "Nouvelle-Aquitaine" },
{ "id": "BELFORT MONTBELIARD TGV", "label": "Belfort-Montbéliard TGV", "city": "Belfort", "lat": 47.5862, "lng": 6.8997, "region": "Bourgogne-Franche-Comté" },
{ "id": "LE CREUSOT MONTCEAU MONTCHANIN", "label": "Le Creusot TGV", "city": "Le Creusot", "lat": 46.7903, "lng": 4.4250, "region": "Bourgogne-Franche-Comté" }
{ "id": "LE CREUSOT MONTCEAU MONTCHANIN", "label": "Le Creusot TGV", "city": "Le Creusot", "lat": 46.7653, "lng": 4.4995, "region": "Bourgogne-Franche-Comté" },
{ "id": "LYON ST EXUPERY TGV.", "label": "Lyon St-Exupéry TGV", "city": "Lyon", "lat": 45.7211, "lng": 5.0750, "region": "Auvergne-Rhône-Alpes" },
{ "id": "VALENCE VILLE", "label": "Valence Ville", "city": "Valence", "lat": 44.9280, "lng": 4.8933, "region": "Auvergne-Rhône-Alpes" },
{ "id": "AVIGNON CENTRE", "label": "Avignon Centre", "city": "Avignon", "lat": 43.9419, "lng": 4.8053, "region": "Provence-Alpes-Côte d'Azur" },
{ "id": "LES ARCS DRAGUIGNAN", "label": "Les Arcs-Draguignan", "city": "Draguignan", "lat": 43.4555, "lng": 6.4825, "region": "Provence-Alpes-Côte d'Azur" },
{ "id": "NIMES PONT DU GARD", "label": "Nîmes Pont-du-Gard", "city": "Nîmes", "lat": 43.8168, "lng": 4.5077, "region": "Occitanie" },
{ "id": "MONTPELLIER SUD DE FRANCE", "label": "Montpellier Sud-de-France", "city": "Montpellier", "lat": 43.5948, "lng": 3.9246, "region": "Occitanie" },
{ "id": "BESANCON FRANCHE COMTE TGV", "label": "Besançon Franche-Comté TGV", "city": "Besançon", "lat": 47.3074, "lng": 5.9532, "region": "Bourgogne-Franche-Comté" },
{ "id": "MACON LOCHE TGV", "label": "Mâcon-Loché TGV", "city": "Mâcon", "lat": 46.2829, "lng": 4.7789, "region": "Bourgogne-Franche-Comté" },
{ "id": "CALAIS FRETHUN", "label": "Calais-Fréthun", "city": "Calais", "lat": 50.9012, "lng": 1.8109, "region": "Hauts-de-France" },
{ "id": "ST PIERRE DES CORPS", "label": "St-Pierre-des-Corps", "city": "Tours", "lat": 47.3861, "lng": 0.7236, "region": "Centre-Val de Loire", "aliases": ["saint pierre des corps"] },
{ "id": "MARSEILLE BLANCARDE", "label": "Marseille Blancarde", "city": "Marseille", "lat": 43.2961, "lng": 5.4066, "region": "Provence-Alpes-Côte d'Azur" },
{ "id": "LES AUBRAIS ORLEANS", "label": "Les Aubrais-Orléans", "city": "Orléans", "lat": 47.9268, "lng": 1.9066, "region": "Centre-Val de Loire" }
]
8 changes: 3 additions & 5 deletions src/data/cities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const CITY_REFERENCE: CityRef[] = [
{ name: "Douai", lat: 50.38, lng: 3.083, region: "Hauts-de-France" },
{ name: "Valenciennes", lat: 50.358, lng: 3.516, region: "Hauts-de-France" },
{ name: "Dunkerque", lat: 51.035, lng: 2.371, region: "Hauts-de-France" },
{ name: "Calais", lat: 50.951, lng: 1.857, region: "Hauts-de-France", aliases: ["calais frethun"] },
{ name: "Calais", lat: 50.951, lng: 1.857, region: "Hauts-de-France", aliases: ["calais ville"] },
{ name: "Béthune", lat: 50.53, lng: 2.641, region: "Hauts-de-France" },
{ name: "Lens", lat: 50.432, lng: 2.831, region: "Hauts-de-France" },
{ name: "Saint-Quentin", lat: 49.848, lng: 3.287, region: "Hauts-de-France" },
Expand Down Expand Up @@ -62,7 +62,7 @@ export const CITY_REFERENCE: CityRef[] = [
{ name: "Meuse", lat: 48.971, lng: 5.27, region: "Grand Est", aliases: ["meuse tgv"] },
// --- Bourgogne-Franche-Comté ----------------------------------------------
{ name: "Chalon-sur-Saône", lat: 46.781, lng: 4.853, region: "Bourgogne-Franche-Comté" },
{ name: "Mâcon", lat: 46.307, lng: 4.829, region: "Bourgogne-Franche-Comté", aliases: ["macon loche", "macon ville"] },
{ name: "Mâcon", lat: 46.307, lng: 4.829, region: "Bourgogne-Franche-Comté", aliases: ["macon ville"] },
{ name: "Montbard", lat: 47.622, lng: 4.337, region: "Bourgogne-Franche-Comté" },
{ name: "Dole", lat: 47.092, lng: 5.49, region: "Bourgogne-Franche-Comté" },
{ name: "Vesoul", lat: 47.621, lng: 6.155, region: "Bourgogne-Franche-Comté" },
Expand Down Expand Up @@ -130,7 +130,6 @@ export const CITY_REFERENCE: CityRef[] = [
{ name: "Redon", lat: 47.651, lng: -2.085, region: "Bretagne" },
{ name: "Auray", lat: 47.668, lng: -2.985, region: "Bretagne" },
// --- Centre-Val de Loire ---------------------------------------------------
{ name: "Orléans", lat: 47.902, lng: 1.905, region: "Centre-Val de Loire" },
{ name: "Blois", lat: 47.587, lng: 1.333, region: "Centre-Val de Loire", aliases: ["blois chambord"] },
{ name: "Bourges", lat: 47.084, lng: 2.396, region: "Centre-Val de Loire" },
{ name: "Châteauroux", lat: 46.811, lng: 1.69, region: "Centre-Val de Loire" },
Expand Down Expand Up @@ -196,7 +195,6 @@ export const CITY_REFERENCE: CityRef[] = [
// --- additional French stations (coverage gaps from the real dataset) ------
{ name: "La Souterraine", lat: 46.237, lng: 1.486, region: "Nouvelle-Aquitaine" },
{ name: "Moulins", lat: 46.565, lng: 3.333, region: "Auvergne-Rhône-Alpes", aliases: ["moulins sur allier"] },
{ name: "Draguignan", lat: 43.537, lng: 6.467, region: "Provence-Alpes-Côte d'Azur", aliases: ["les arcs draguignan", "les arcs"] },
{ name: "Cahors", lat: 44.448, lng: 1.435, region: "Occitanie" },
{ name: "Gourdon", lat: 44.738, lng: 1.384, region: "Occitanie" },
{ name: "Souillac", lat: 44.895, lng: 1.471, region: "Occitanie" },
Expand Down Expand Up @@ -281,7 +279,7 @@ export const CITY_REFERENCE: CityRef[] = [
{ name: "Die", lat: 44.754, lng: 5.37, region: "Auvergne-Rhône-Alpes" },
{ name: "Landry", lat: 45.573, lng: 6.736, region: "Auvergne-Rhône-Alpes" },
{ name: "Luc-en-Diois", lat: 44.614, lng: 5.446, region: "Auvergne-Rhône-Alpes" },
{ name: "Nurieux", lat: 46.18, lng: 5.47, region: "Auvergne-Rhône-Alpes" },
{ name: "Nurieux", lat: 46.184, lng: 5.533, region: "Auvergne-Rhône-Alpes" },
{ name: "Saint-Avre-La Chambre", lat: 45.38, lng: 6.3, region: "Auvergne-Rhône-Alpes" },
{ name: "Saint-Jean-de-Maurienne", lat: 45.276, lng: 6.346, region: "Auvergne-Rhône-Alpes", aliases: ["saint jean de maurienne arvan"] },
{ name: "Saint-Michel-Valloire", lat: 45.218, lng: 6.47, region: "Auvergne-Rhône-Alpes" },
Expand Down
15 changes: 15 additions & 0 deletions tests/stations.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it, expect } from "vitest";
import type { Station } from "../src/types";
import { StationRegistry, normalizeText, prettyLabel, isAirportStation } from "../src/data/stations";
import { haversineKm } from "../src/util/geo";
import stationData from "../data/stations.json";

const registry = new StationRegistry(stationData as Station[]);
Expand Down Expand Up @@ -79,6 +80,20 @@ describe("StationRegistry.addMissing", () => {
expect(r.city("TGV HAUTE PICARDIE")).toBe("Haute-Picardie");
});

it("plots an out-of-town station at the station, not at its city centre", () => {
const r = new StationRegistry(stationData as Station[]);
r.addMissing(["LYON ST EXUPERY TGV.", "LYON (intramuros)", "VALENCE TGV AUVERGNE RHONE ALPES", "VALENCE VILLE", "CALAIS FRETHUN", "CALAIS VILLE", "TOURS", "ST PIERRE DES CORPS", "MARSEILLE ST CHARLES", "MARSEILLE BLANCARDE", "LES AUBRAIS ORLEANS"]);
const km = (a: string, b: string): number => haversineKm(r.coords(a)!, r.coords(b)!);
expect(km("LYON ST EXUPERY TGV.", "LYON (intramuros)")).toBeGreaterThan(15);
expect(km("VALENCE TGV AUVERGNE RHONE ALPES", "VALENCE VILLE")).toBeGreaterThan(8);
expect(km("CALAIS FRETHUN", "CALAIS VILLE")).toBeGreaterThan(5);
// Two stations of one town on one point hide each other from the nearby search.
expect(km("TOURS", "ST PIERRE DES CORPS")).toBeGreaterThan(2);
expect(km("MARSEILLE ST CHARLES", "MARSEILLE BLANCARDE")).toBeGreaterThan(2);
expect(r.city("LYON ST EXUPERY TGV.")).toBe("Lyon"); // guide link still opens the city
expect(r.city("LES AUBRAIS ORLEANS")).toBe("Orléans");
});

it("dedupes label collisions, preferring the id present in the dataset", () => {
const r = new StationRegistry(stationData as Station[]);
// The curated registry has id "LILLE"; a dataset variant renders the same.
Expand Down
Loading