facilmap/server/src/routing/ors.ts

138 wiersze
4.6 KiB
TypeScript

import config from "../config";
import { calculateDistance, DecodedRouteMode } from "facilmap-utils";
import { ExtraInfo, Point } from "facilmap-types";
import { throttle } from "../utils/utils";
import { RawRouteInfo } from "./routing";
import fetch from "node-fetch";
if (!config.orsToken)
console.error("Warning: No ORS token configured, calculating routes will fail. Please set ORS_TOKEN in the environment or in config.env.");
const ROUTING_URL = `https://api.openrouteservice.org/v2/directions`;
const ROUTING_MODES: Record<string, string> = {
"car-": "driving-car",
"car-hgv": "driving-hgv",
"bicycle-": "cycling-regular",
"bicycle-road": "cycling-road",
// "bicycle-safe": "cycling-safe",
"bicycle-mountain": "cycling-mountain",
// "bicycle-tour": "cycling-tour",
"bicycle-electric": "cycling-electric",
"pedestrian-": "foot-walking",
"pedestrian-hiking": "foot-hiking",
"pedestrian-wheelchair": "wheelchair",
};
const MAX_DISTANCE = {
car: 6000000,
bicycle: 30000000,
pedestrian: 20000000
} as Record<DecodedRouteMode['mode'], number>;
export function getMaximumDistanceBetweenRoutePoints(decodedMode: DecodedRouteMode): number {
return MAX_DISTANCE[decodedMode.mode];
}
export const calculateORSRoute = throttle(calculateRouteInternal, 4);
async function calculateRouteInternal(points: Point[], decodedMode: DecodedRouteMode): Promise<RawRouteInfo> {
if (!config.orsToken)
throw new Error("Warning: No ORS token configured. Please ask the administrator to set ORS_TOKEN in the environment or in config.env.");
let currentGroup: Point[] = [];
const coordGroups: Point[][] = [currentGroup];
for(const point of points) {
if(calculateDistance(currentGroup.concat([point])) >= MAX_DISTANCE[decodedMode.mode]) {
if(currentGroup.length == 1)
throw new Error("Too much distance between route points. Consider adding some via points.");
coordGroups.push(currentGroup = [currentGroup[currentGroup.length-1]]);
if(calculateDistance(currentGroup.concat([point])) >= MAX_DISTANCE[decodedMode.mode])
throw new Error("Too much distance between route points. Consider adding some via points.");
}
currentGroup.push(point);
}
const results = await Promise.all(coordGroups.map((coords) => {
const req: any = {
coordinates: coords.map((point) => [point.lon, point.lat]),
radiuses: coords.map(() => -1),
instructions: false
};
if(decodedMode.details) {
req.elevation = true;
req.extra_info = [ "surface", "waytype", "steepness" ];
if(decodedMode.mode == "car") {
req.extra_info.push("tollways");
}
}
if(decodedMode.avoid) {
req.options = {
avoid_features: decodedMode.avoid
};
}
if(decodedMode.preference)
req.preference = decodedMode.preference;
return fetch(`${ROUTING_URL}/${ROUTING_MODES[`${decodedMode.mode}-${decodedMode.type || ""}`]}/geojson`, {
method: "POST",
headers: {
...(config.orsToken ? { "Authorization": config.orsToken } : {}),
"Accept": "*/*", // Server sends application/geo+json
"Content-type": "application/json"
},
body: JSON.stringify(req)
}).then((res) => res.json());
}));
const ret = {
trackPoints: [] as Array<Point & { ele?: number }>,
distance: 0,
time: 0,
ascent: decodedMode.details ? 0 : undefined,
descent: decodedMode.details ? 0 : undefined,
extraInfo: decodedMode.details ? {} as ExtraInfo : undefined
};
for(const body of results) {
if(body?.error) {
throw new Error(body.error?.message || body.error);
}
if (body?.metadata?.system_message)
console.log("OpenRouteService:", body?.metadata?.system_message);
if(!body?.features?.[0])
throw new Error("Invalid response from routing server.");
let idxAdd = ret.trackPoints.length;
const trackPoints = body.features[0].geometry.coordinates.map((it: any) => ({ lat: it[1], lon: it[0], ele: it[2] }));
if(trackPoints.length > 0 && ret.trackPoints.length > 0 && trackPoints[0].lat == ret.trackPoints[ret.trackPoints.length-1].lat && trackPoints[0].lon == ret.trackPoints[ret.trackPoints.length-1].lon) {
trackPoints.shift();
idxAdd--;
}
ret.trackPoints.push(...trackPoints);
ret.distance += body.features[0].properties.summary.distance/1000;
ret.time += body.features[0].properties.summary.duration;
if(decodedMode.details) {
ret.ascent += body.features[0].properties.ascent;
ret.descent += body.features[0].properties.descent;
for(const i of Object.keys(body.features[0].properties.extras)) {
if(!ret.extraInfo![i])
ret.extraInfo![i] = [];
ret.extraInfo![i].push(...body.features[0].properties.extras[i].values.map((v: any) => ([v[0]+idxAdd, v[1]+idxAdd, v[2]])));
}
}
}
return ret;
}