From d1998ef2965e22951dccd1cde523c47cb0a2ca81 Mon Sep 17 00:00:00 2001 From: Xav Dmz Date: Tue, 16 Jan 2024 16:36:03 +0100 Subject: [PATCH] WIP: OSRM controller implemented, but unpolished and untested. TODO : * complete logging and varible check * test the code --- .../apis/osrm/1.0.0/controller/controller.js | 67 ++++++++--- src/js/apis/osrm/1.0.0/index.js | 111 ++++++------------ src/js/sources/osrmSource.js | 5 +- 3 files changed, 92 insertions(+), 91 deletions(-) diff --git a/src/js/apis/osrm/1.0.0/controller/controller.js b/src/js/apis/osrm/1.0.0/controller/controller.js index cc0fb2c2..977a7315 100644 --- a/src/js/apis/osrm/1.0.0/controller/controller.js +++ b/src/js/apis/osrm/1.0.0/controller/controller.js @@ -5,6 +5,7 @@ const log4js = require('log4js'); const polyline = require('@mapbox/polyline'); const Turf = require('@turf/turf'); +const copyManager = require('../../../../utils/copyManager') const Distance = require('../../../../geography/distance'); const Duration = require('../../../../time/duration'); const errorManager = require('../../../../utils/errorManager'); @@ -296,11 +297,10 @@ module.exports = { let userResponse = { "code": routeResponse.engineExtras.code }; - let askedProjection = routeRequest.start.projection; // Waypoints - let waypointArray = JSON.parse(JSON.stringify(routeResponse.engineExtras.waypoints)); + let waypointArray = copyManager.deepCopy(routeResponse.engineExtras.waypoints); let startingPoint = routeResponse.routes[0].portions[0].start; waypointArray[0].location = [startingPoint.x, startingPoint.y]; for (let i = 1; i < waypointArray.length; i++) { @@ -309,30 +309,65 @@ module.exports = { } userResponse.waypoints = waypointArray; + // Routes let routeArray = new Array(); for (let routeIdx = 0; routeIdx < routeResponse.routes.length; routeIdx++) { - // for (let route in routeResponse.routes) { - let simpleRoute = routeResponse.routes[routeIdx]; - let extraRoute = routeResponse.engineExtras.routes[routeIdx]; - routeArray[routeIdx] = { - "distance": simpleRoute.distance, - "duration": simpleRoute.duration, - "geometry": simpleRoute.geometry.getGeometryWithFormat(routeRequest.geometryFormat), - "legs": [] - }; + let simpleRoute = routeResponse.routes[routeIdx]; // from road2 standard response + let extraRoute = routeResponse.engineExtras.routes[routeIdx]; // from engine specific extras + // both sources will be fused to craft a response compliant with OSRM's official API definition + // Legs (Road2's "portions") let legArray = new Array(); - for (let legIdx = 0; legIdx < route.portions.length; legIdx++) { - let portion = route.portions[legIdx] - legArray = { + for (let legIdx = 0; legIdx < simpleRoute.portions.length; legIdx++) { + let portion = simpleRoute.portions[legIdx]; // from road2 standard response + let leg = extraRoute.legs[legIdx]; // from engine specific extras + legArray[legIdx] = { "distance": portion.distance, - "duration": portion.duration, - "steps": [] + "duration": portion.duration }; + + // Steps (optional) + let stepArray = new Array(); + if (routeRequest.computeSteps && portion.steps.length !== 0) { + for (let stepIdx = 0; stepIdx < portion.steps.length; stepIdx++) { + let simpleStep = portion.steps[stepIdx]; // from road2 standard response + let extraStep = leg.steps[stepIdx]; // from engine specific extras + stepArray[stepIdx] = { + "distance": simpleStep.distance, + "duration": simpleStep.duration, + "geometry": simpleStep.geometry.getGeometryWithFormat(routeRequest.geometryFormat), + "intersections": copyManager.deepCopy(extraStep.intersections), + "maneuver": { + "type": extraStep.instruction.type + }, + "mode": extraStep.mode, + "name": simpleStep.name + }; + if (simpleStep.instruction.modifier) { + stepArray[stepIdx].maneuver.modifier = simpleStep.instruction.modifier; + } + if (simpleStep.instruction.exit) { + stepArray[stepIdx].maneuver.exit = simpleStep.instruction.exit; + } + } + legArray[legIdx].steps = stepArray; + } else { + LOGGER.debug("no steps asked by user"); + } } + + routeArray[routeIdx] = { + "distance": simpleRoute.distance, + "duration": simpleRoute.duration, + "geometry": simpleRoute.geometry.getGeometryWithFormat(routeRequest.geometryFormat), + "legs": legArray + }; } + // Finalze userResponse + userResponse.routes = routeArray; + return userResponse; } } \ No newline at end of file diff --git a/src/js/apis/osrm/1.0.0/index.js b/src/js/apis/osrm/1.0.0/index.js index ca584114..eda1f6b7 100644 --- a/src/js/apis/osrm/1.0.0/index.js +++ b/src/js/apis/osrm/1.0.0/index.js @@ -11,79 +11,47 @@ const swaggerUi = require('swagger-ui-express'); var LOGGER = log4js.getLogger("OSRM"); var router = express.Router(); -// POST -// --- -// Pour cette API, on va permettre la lecture des requêtes POST en parsant les contenus du type application/json -router.use(express.json( - // Fonctions utilisées pour vérifier le body d'un POST et ainsi récupérer les erreurs - { - type: (req) => { - // Le seul content-type accepté a toujours été application/json, on rend cela plus explicite - // Cette fonction permet d'arrêter le traitement de la requête si le content-type n'est pas correct. - // Sans elle, le traitement continue. - if (req.get('Content-Type') !== "application/json") { - throw errorManager.createError(" Wrong Content-Type. Must be 'application/json' ", 400); - } else { - return true; - } - }, - verify: (req, res, buf, encoding) => { - // Cette fonction permet de vérifier que le JSON envoyé est valide. - // Si ce n'est pas le cas, le traitement de la requête est arrêté. - try { - JSON.parse(buf); - } catch (error) { - throw errorManager.createError("Invalid request body. Error during the parsing of the body: " + error.message, 400); - } - } - } -)); -// --- - -// Accueil de l'API +// API entrypoint router.all("/", function(req, res) { - LOGGER.debug("requete sur /osrm/1.0.0/"); - res.send("Road2 via l'API OSRM 1.0.0"); + LOGGER.debug("request on /osrm/1.0.0/"); + res.send("Road2 via OSRM API 1.0.0"); }); // swagger-ui var apiJsonPath = path.join(__dirname, '..', '..', '..','..','..', 'documentation','apis','osrm', '1.0.0', 'api.json'); -LOGGER.info("Utilisation fichier .json '"+ apiJsonPath + "' pour initialisation swagger-ui de l'API OSRM en version 1.0.0"); +LOGGER.info("using file '"+ apiJsonPath + "' to initialize swagger-ui for OSRM API version 1.0.0"); var swaggerDocument = require(apiJsonPath); router.use('/openapi', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); // GetCapabilities router.all("/resources", function(req, res) { - LOGGER.debug("requete sur /osrm/1.0.0/resources?"); + LOGGER.debug("request on /osrm/1.0.0/resources?"); - // récupération du service + // get service let service = req.app.get("service"); - // récupération du uid + // get uid let uid = service.apisManager.getApi("osrm","1.0.0").uid; LOGGER.debug(uid); - // récupération du getCapabilities précalculé dans init.js + // get getCapabilities from init.js let getCapabilities = req.app.get(uid + "-getcap"); LOGGER.debug(getCapabilities); - // Modification si Host ou X-Forwarded-Host précisè dans l'en-tête de la requête - // il est récupéré par express dans req.host + // Change base url in GetCapabilties if "Host" or "X-Forwarded-Host" is specified in request's headers + // Host is stored by expres in req.hostname if (req.hostname) { - - // TODO : corriger avec quelque chose du genre ^http(s)?:\/\/(.+) puis split au premier / let regexpHost = /^http[s]?:\/\/[\w\d:-_\.]*\//; - try { getCapabilities.info.url = getCapabilities.info.url.replace(regexpHost, req.protocol + "://" + req.hostname + "/"); } catch(error) { - // on renvoit le getcap par défaut + // apply default service url in GetCapabilities } } else { - // il y a déjà une valeur par défaut + // apply default service url in GetCapabilities } res.set('content-type', 'application/json'); @@ -91,24 +59,23 @@ router.all("/resources", function(req, res) { }); -// Route -// Pour effectuer un calcul d'itinéraire +// Route: routing request router.route("/:resource/:profile/:optimization/route/v1/_/:coordinates") .get(async function(req, res, next) { - LOGGER.debug("requete GET sur /osrm/1.0.0/:resource"); + LOGGER.debug("GET request on /osrm/1.0.0/:resource"); LOGGER.debug(req.originalUrl); - // On récupère l'instance de Service pour faire les calculs + // get service instance let service = req.app.get("service"); - // on vérifie que l'on peut faire cette opération sur l'instance du service + // check if operation is permitted on this service instance if (!service.verifyAvailabilityOperation("route")) { return next(errorManager.createError(" Operation not permitted on this service ", 400)); } - // on récupère l'ensemble des paramètres de la requête + // get request parameters, both from path and query let path_parameters = req.params let query_parameters = req.query; let parameters = {} @@ -122,33 +89,31 @@ router.route("/:resource/:profile/:optimization/route/v1/_/:coordinates") try { - // Vérification des paramètres de la requête + // Check request parameters const routeRequest = controller.checkRouteParameters(parameters, service, "GET"); LOGGER.debug(routeRequest); + // Send to service and get response object + const routeResponse = await service.computeRequest(routeRequest); + LOGGER.debug(routeResponse); + // Format response + const userResponse = controller.writeRouteResponse(routeRequest, routeResponse, service); + LOGGER.debug(userResponse); - } - - - return next(errorManager.createError(" Operation not implemented yet on this service ", 501)); - - }) - - .post(async function(req, res, next) { + res.set('content-type', 'application/json'); + res.status(200).json(userResponse); } catch (error) { return next(error); - LOGGER.debug("requete POST sur /osrm/1.0.0/:resource/"); - LOGGER.debug(req.originalUrl); - return next(errorManager.createError(" Operation not implemented yet on this service ", 501)); + } }); -// Gestion des erreurs -// Cette partie doit être placée après la définition des routes normales +// Error management +// This part must be placed after normal routes definitions // --- router.use(logError); router.use(sendError); -// Celui-ci doit être le dernier pour renvoyer un 404 si toutes les autres routes font appel à next +// This must be the last item to send an HTTP 404 error if evry route calls next. router.use(notFoundError); // --- @@ -156,7 +121,7 @@ router.use(notFoundError); * * @function * @name logError -* @description Callback pour écrire l'erreur dans les logs +* @description Callback to log error * */ @@ -186,19 +151,19 @@ function logError(err, req, res, next) { * * @function * @name sendError -* @description Callback pour envoyer l'erreur au client +* @description Callback to send error to client * */ function sendError(err, req, res, next) { - // On ne veut pas le même comportement en prod et en dev + // Behaviour should differ between production and development environments if (process.env.NODE_ENV === "production") { if (err.status) { - // S'il y a un status dans le code, alors cela veut dire qu'on veut remonter l'erreur au client + // if error has a status, this error should be sent to the client res.status(err.status); res.json({ error: {errorType: err.code, message: err.message}}); } else { - // S'il n'y a pas de status dans le code alors on ne veut pas remonter l'erreur + // if error has no status, this error's details should not be sent to the client res.status(500); res.json({ error: {errorType: "internal", message: "Internal Server Error"}}); } @@ -207,11 +172,11 @@ function sendError(err, req, res, next) { res.json({ error: {errorType: err.code, message: err.message, stack: err.stack, - // utile lorsqu'une erreur sql remonte + // useful for SQL errors more: err }}); } else { - // En dev, on veut faire remonter n'importe quelle erreur + // in the development environment, every error should be sent to clients res.status(err.status || 500); res.json({ error: {errorType: err.code, message: err.message}}); } @@ -222,7 +187,7 @@ function sendError(err, req, res, next) { * * @function * @name sendError -* @description Callback pour envoyer l'erreur au client +* @description Callback to send HTTP "Not Found" error to client * */ diff --git a/src/js/sources/osrmSource.js b/src/js/sources/osrmSource.js index c5e76cab..d7f48bd2 100644 --- a/src/js/sources/osrmSource.js +++ b/src/js/sources/osrmSource.js @@ -10,6 +10,7 @@ const Point = require('../geometry/point'); const Step = require('../responses/step'); const Distance = require('../geography/distance'); const Duration = require('../time/duration'); +const copyManager = require('../../../../utils/copyManager') const errorManager = require('../utils/errorManager'); const log4js = require('log4js'); @@ -554,7 +555,7 @@ module.exports = class osrmSource extends Source { nativeIntersections = new Array(); for (let intersectionIndex = 0; intersectionIndex < currentOsrmRouteStep.intersections.length; intersectionIndex++) { let currentIntersection = currentOsrmRouteStep.intersections[intersectionIndex]; - nativeIntersections[intersectionIndex] = JSON.parse(JSON.stringify(currentIntersection)); + nativeIntersections[intersectionIndex] = copyManager.deepCopy(currentIntersection); nativeIntersections[intersectionIndex].location = new Point(currentIntersection.location[0], currentIntersection.location[1], super.projection) if (!nativeIntersections[intersectionIndex].location.transform(askedProjection)) { throw errorManager.createError(" Error during reprojection of intersection in OSRM response. "); @@ -574,7 +575,7 @@ module.exports = class osrmSource extends Source { } routeResponse.routes = routes; - routeResponse.engineExtras = engineExtras: + routeResponse.engineExtras = engineExtras; return routeResponse;