Import initial : code en production sur Hetzner
Some checks are pending
CI / tests (push) Waiting to run
CI / deploiement (push) Blocked by required conditions

This commit is contained in:
Adam Belkacemi 2026-09-07 18:21:38 +00:00
commit cf6ee64e31
43 changed files with 14404 additions and 0 deletions

89
shared/polyline.ts Normal file
View file

@ -0,0 +1,89 @@
import type { Coord } from './types'
/**
* Decode le format « encoded polyline » de Google, utilise par MOTIS.
* La precision n'est pas universelle : Google encode a 1e5, MOTIS a 1e7.
* Se tromper d'un facteur 100 place le trace a des milliers de kilometres.
*/
export function decodePolyline(encoded: string, precision = 5): Coord[] {
const factor = 10 ** precision
const coords: Coord[] = []
let index = 0
let lat = 0
let lon = 0
while (index < encoded.length) {
let result = 0
let shift = 0
let byte: number
do {
byte = encoded.charCodeAt(index++) - 63
result |= (byte & 0x1F) << shift
shift += 5
} while (byte >= 0x20)
lat += result & 1 ? ~(result >> 1) : result >> 1
result = 0
shift = 0
do {
byte = encoded.charCodeAt(index++) - 63
result |= (byte & 0x1F) << shift
shift += 5
} while (byte >= 0x20)
lon += result & 1 ? ~(result >> 1) : result >> 1
// Ordre GeoJSON : longitude d'abord, comme l'attend MapLibre.
coords.push([lon / factor, lat / factor])
}
return coords
}
/**
* Reduit le nombre de points d'un trace.
* Un aller Lille-Paris peut compter plusieurs milliers de points ; au-dela
* d'un millier, l'oeil ne voit plus la difference mais le reseau, si.
*/
export function thin(coords: Coord[], max = 800): Coord[] {
if (coords.length <= max) return coords
const step = Math.ceil(coords.length / max)
const out = coords.filter((_, i) => i % step === 0)
// Le dernier point est le terminus : il ne doit jamais sauter.
if (out[out.length - 1] !== coords[coords.length - 1]) out.push(coords[coords.length - 1]!)
return out
}
/**
* Renvoie le point situe a un ratio donne de la distance cumulee le long
* du trace. midpointAt(coords, 0.5) donne le vrai milieu geometrique, pas
* le point d'index milieu qui atterrit dans le vide sur un trajet inegal.
* Utilise Haversine simplifie en distance euclidienne : suffisant pour
* placer une bulle a l'echelle d'un trajet inter-villes.
*/
export function midpointAt(coords: Coord[], ratio = 0.5): Coord {
if (coords.length === 0) throw new Error('trace vide')
if (coords.length === 1) return coords[0]!
if (ratio <= 0) return coords[0]!
if (ratio >= 1) return coords[coords.length - 1]!
const distances: number[] = [0]
let total = 0
for (let i = 1; i < coords.length; i++) {
const [x1, y1] = coords[i - 1]!
const [x2, y2] = coords[i]!
total += Math.hypot(x2 - x1, y2 - y1)
distances.push(total)
}
const target = total * ratio
for (let i = 1; i < distances.length; i++) {
if (distances[i]! >= target) {
const [x1, y1] = coords[i - 1]!
const [x2, y2] = coords[i]!
const segLength = distances[i]! - distances[i - 1]!
const segRatio = segLength === 0 ? 0 : (target - distances[i - 1]!) / segLength
return [x1 + (x2 - x1) * segRatio, y1 + (y2 - y1) * segRatio]
}
}
return coords[coords.length - 1]!
}