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]! }