89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
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]!
|
|
}
|