From 04abc0fb04acb7b79e5d4af8139d402c1b918089 Mon Sep 17 00:00:00 2001 From: Xav Dmz Date: Wed, 15 Nov 2023 16:37:13 +0100 Subject: [PATCH 1/7] WIP: adding OSRM native API. --- docker/config/service.json | 4 + documentation/apis/osrm/1.0.0/api.json | 2 +- .../apis/osrm/1.0.0/controller/controller.js | 244 ++++ src/js/apis/osrm/1.0.0/index.js | 234 ++++ src/js/apis/osrm/1.0.0/init.js | 1017 +++++++++++++++++ src/js/apis/osrm/1.0.0/update.js | 48 + src/js/apis/simple/1.0.0/update.js | 4 +- 7 files changed, 1550 insertions(+), 3 deletions(-) create mode 100644 src/js/apis/osrm/1.0.0/controller/controller.js create mode 100644 src/js/apis/osrm/1.0.0/index.js create mode 100644 src/js/apis/osrm/1.0.0/init.js create mode 100644 src/js/apis/osrm/1.0.0/update.js diff --git a/docker/config/service.json b/docker/config/service.json index b3b4c686..1dec91f6 100644 --- a/docker/config/service.json +++ b/docker/config/service.json @@ -58,6 +58,10 @@ { "name" : "simple", "version" : "1.0.0" + }, + { + "name" : "osrm", + "version" : "1.0.0" } ] } diff --git a/documentation/apis/osrm/1.0.0/api.json b/documentation/apis/osrm/1.0.0/api.json index 024d2444..d2d7a1de 100644 --- a/documentation/apis/osrm/1.0.0/api.json +++ b/documentation/apis/osrm/1.0.0/api.json @@ -71,7 +71,7 @@ }, { "name": "coordinates", - "in": "query", + "in": "path", "description": "String of format {longitude},{latitude};{longitude},{latitude}[;{longitude},{latitude} ...] or polyline({polyline}).", "required": true, "schema": { diff --git a/src/js/apis/osrm/1.0.0/controller/controller.js b/src/js/apis/osrm/1.0.0/controller/controller.js new file mode 100644 index 00000000..89f94547 --- /dev/null +++ b/src/js/apis/osrm/1.0.0/controller/controller.js @@ -0,0 +1,244 @@ +'use strict'; + +const log4js = require('log4js'); + +const polyline = require('@mapbox/polyline'); +const Turf = require('@turf/turf'); + +const Distance = require('../../../../geography/distance'); +const Duration = require('../../../../time/duration'); +const errorManager = require('../../../../utils/errorManager'); +const NearestRequest = require('../../../../requests/nearestRequest'); +const Point = require('../../../../geometry/point'); +const RouteRequest = require('../../../../requests/routeRequest'); + +var LOGGER = log4js.getLogger("CONTROLLER"); + +module.exports = { + + /** + * + * @function + * @name checkRouteParameters + * @description Vérification des paramètres d'une requête sur /route + * @param {object} parameters - ensemble des paramètres de la requête + * @param {object} service - Instance de la classe Service + * @param {string} method - Méthode de la requête + * @return {object} RouteRequest - Instance de la classe RouteRequest + * + */ + + checkRouteParameters: function(parameters, service, method) { + + let resource; + let start = {}; + let end = {}; + let profile; + let optimization; + let tmpStringCoordinates; + let askedProjection; + + LOGGER.debug("checkRouteParameters()"); + + // Resource + if (!parameters.resource) { + throw errorManager.createError(" Parameter 'resourceId' not found ", 400); + } else { + + LOGGER.debug("user resource:"); + LOGGER.debug(parameters.resource); + + // Vérification de la disponibilité de la ressource et de la compatibilité de son type avec la requête + if (!service.verifyResourceExistenceById(parameters.resource)) { + throw errorManager.createError(" Parameter 'resourceId' is invalid: it does not exist on this service ", 400); + } else { + + resource = service.getResourceById(parameters.resource); + // On vérifie que la ressource peut accepter cette opération + if (!resource.verifyAvailabilityOperation("route")){ + throw errorManager.createError(" Operation 'route' is not permitted on this resource ", 400); + } else { + LOGGER.debug("operation route valide on this resource"); + } + + } + } + + // On récupère l'opération route pour faire des vérifications + let routeOperation = resource.getOperationById("route"); + + + // Profile and Optimization + // --- + + if (!parameters.profile) { + throw errorManager.createError(" Parameter 'profileId' not found", 400); + } else { + LOGGER.debug("user profile:"); + LOGGER.debug(parameters.profile); + // Vérification de la validité du paramètre + let validity = routeOperation.getParameterById("profile").check(parameters.profile); + if (validity.code !== "ok") { + throw errorManager.createError(" Parameter 'profileId' is invalid: " + validity.message, 400); + } else { + profile = parameters.profile; + LOGGER.debug("user profile valide"); + } + } + + if (!parameters.optimization) { + throw errorManager.createError(" Parameter 'optimizationId' not found", 400); + } else { + LOGGER.debug("user optimization:"); + LOGGER.debug(parameters.optimization); + // Vérification de la validité du paramètre + let validity = routeOperation.getParameterById("optimization").check(parameters.optimization); + if (validity.code !== "ok") { + throw errorManager.createError(" Parameter 'optimizationId' is invalid: " + validity.message, 400); + } else { + optimization = parameters.optimization; + LOGGER.debug("user optimization is valid"); + } + } + + // Paramètres spécifiques à l'API OSRM + // coordinates (2 possible formats) + if (!parameters.coordinates) { + throw errorManager.createError(" Parameter 'coordinates' not found", 400); + } else { + LOGGER.debug("raw coordinates:"); + LOGGER.debug(parameters.coordinates); +y + let raw_string_pattern = /(-?\d+(\.\d+)?,-?\d+(\.\d+)?;){1,}-?\d+(\.\d+)?,-?\d+(\.\d+)?/; + let polyline_pattern = /polyline\(\S+\)/; + + // TODO : extract coordinates in a single format + if (raw_string_pattern.test(parameters.coordinates)) { + + } else if (polyline_pattern.test(parameters.coordinates)) { + + } else { + throw errorManager.createError(" Parameter 'coordinates' is invalid: does not match allowed formats", 400); + } + + } + + + // alternatives + + // steps + + // annotations + + // geometries + + // overview + + // continue_straight + + + // On définit la routeRequest avec les paramètres obligatoires + let routeRequest = new RouteRequest(parameters.resource, start, end, profile, optimization); + + LOGGER.debug(routeRequest); + + // Vérification de la validité du profile et de sa compatibilité avec l'optimisation + if (!resource.checkSourceAvailibilityFromRequest(routeRequest)) { + throw errorManager.createError(" Parameters 'profile' and 'optimization' are not compatible ", 400); + } else { + LOGGER.debug("profile et optimization compatibles"); + } + + + + }, + + /** + * + * @function + * @name writeRouteResponse + * @description Ré-écriture de la réponse d'un moteur pour une requête sur /route + * @param {object} RouteRequest - Instance de la classe RouteRequest + * @param {object} RouteResponse - Instance de la classe RouteResponse + * @param {object} service - Instance de la classe Service + * @return {object} userResponse - Réponse envoyée à l'utilisateur + * + */ + + writeRouteResponse: function(routeRequest, routeResponse, service) { + + + }, + + /** + * + * @function + * @name convertPostArrayToGetParameters + * @description Transformation d'un paramètre POST en chaîne de caractères pour avoir l'équivalent d'un paramètre GET. + * @param {object} userParameter - Paramètre POST donné par l'utilisateur + * @param {Parameter} serviceParameter - Instance de la classe Parameter + * @param {string} parameterName - Nom du paramètre converti + * @return {string|array} Paramètre en GET + * + */ + + convertPostArrayToGetParameters: function(userParameter, serviceParameter, parameterName) { + + LOGGER.debug("convertPostArrayToGetParameters() for " + parameterName); + + let finalParameter = ""; + let separator = ""; + + if (serviceParameter.explode === "false") { + if (serviceParameter.style === "pipeDelimited") { + separator = "|"; + LOGGER.debug("separateur trouve pour ce parametre"); + } else { + // ne doit pas arriver + throw errorManager.createError(" Error in parameter configuration. "); + } + } else { + // C'est déjà un tableau qu'on retourne car c'est ce qu'on attend pour le GET + LOGGER.debug("nothing to do for this parameter"); + return userParameter; + } + + if (!Array.isArray(userParameter)) { + throw errorManager.createError(" The parameter " + parameterName + " is not an array. ", 400); + } else { + LOGGER.debug("Le parametre est un tableau"); + } + if (userParameter.length === 0) { + throw errorManager.createError(" The parameter " + parameterName + " is an empty array. ", 400); + } else { + LOGGER.debug("Le parametre est un tableau non vide"); + } + + try { + if (typeof userParameter[0] !== "object") { + finalParameter = userParameter[0]; + } else { + finalParameter = JSON.stringify(userParameter[0]); + } + } catch(err) { + throw errorManager.createError(" The parameter " + parameterName + " can't be converted to a string. ", 400); + } + + for (let i = 1; i < userParameter.length; i++) { + try { + //TODO: vérifier que l'on peut mettre i à la place de 0 + if (typeof userParameter[0] !== "object") { + finalParameter = finalParameter + separator + userParameter[i]; + } else { + finalParameter = finalParameter + separator + JSON.stringify(userParameter[i]); + } + } catch(err) { + throw errorManager.createError(" The parameter " + parameterName + " can't be converted to a string. ", 400); + } + } + + return finalParameter; + + } + +} \ 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 new file mode 100644 index 00000000..dd449ea3 --- /dev/null +++ b/src/js/apis/osrm/1.0.0/index.js @@ -0,0 +1,234 @@ +'use strict'; + + +const path = require('path'); +const express = require('express'); +const log4js = require('log4js'); +const controller = require('./controller/controller'); +const errorManager = require('../../../utils/errorManager'); +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 +router.all("/", function(req, res) { + LOGGER.debug("requete sur /osrm/1.0.0/"); + res.send("Road2 via l'API OSRM 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"); +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?"); + + // récupération du service + let service = req.app.get("service"); + + // récupération du uid + let uid = service.apisManager.getApi("osrm","1.0.0").uid; + LOGGER.debug(uid); + + // récupération du getCapabilities précalculé dans 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 + 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 + } + + } else { + // il y a déjà une valeur par défaut + } + + res.set('content-type', 'application/json'); + res.status(200).json(getCapabilities); + +}); + +// Route +// Pour effectuer un calcul d'itinéraire +router.route("/resource/:resource/:profile/:optimization/route/v1/profile/:coordinates") + + .get(async function(req, res, next) { + + LOGGER.debug("requete GET sur /osrm/1.0.0/resource/"); + LOGGER.debug(req.originalUrl); + + // On récupère l'instance de Service pour faire les calculs + let service = req.app.get("service"); + + // on vérifie que l'on peut faire cette opération sur l'instance du service + 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 + let path_parameters = req.params + let query_parameters = req.query; + let parameters = {} + for (const key in query_parameters) { + parameters[key] = query_parameters[key] + } + for (const key in path_parameters) { + parameters[key] = path_parameters[key] + } + LOGGER.debug(parameters); + + try { + + // Vérification des paramètres de la requête + const routeRequest = controller.checkRouteParameters(parameters, service, "GET"); + LOGGER.debug(routeRequest); + + } catch (error) { + return next(error); + } + + + return next(errorManager.createError(" Operation not implemented yet on this service ", 501)); + + }) + + .post(async function(req, res, next) { + + 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 +// --- +router.use(logError); +router.use(sendError); +// Celui-ci doit être le dernier pour renvoyer un 404 si toutes les autres routes font appel à next +router.use(notFoundError); +// --- + +/** +* +* @function +* @name logError +* @description Callback pour écrire l'erreur dans les logs +* +*/ + +function logError(err, req, res, next) { + + let message = { + request: req.originalUrl, + query: req.query, + body: req.body, + error: { + errorType: err.code, + message: err.message, + stack: err.stack + } + }; + + if (err.status) { + LOGGER.debug(message); + } else { + LOGGER.error(message); + } + + next(err); +} + +/** +* +* @function +* @name sendError +* @description Callback pour envoyer l'erreur au client +* +*/ + +function sendError(err, req, res, next) { + // On ne veut pas le même comportement en prod et en dev + 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 + 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 + res.status(500); + res.json({ error: {errorType: "internal", message: "Internal Server Error"}}); + } + } else if ((process.env.NODE_ENV === "debug")) { + res.status(err.status || 500); + res.json({ error: {errorType: err.code, + message: err.message, + stack: err.stack, + // utile lorsqu'une erreur sql remonte + more: err + }}); + } else { + // En dev, on veut faire remonter n'importe quelle erreur + res.status(err.status || 500); + res.json({ error: {errorType: err.code, message: err.message}}); + } + +} + +/** +* +* @function +* @name sendError +* @description Callback pour envoyer l'erreur au client +* +*/ + +function notFoundError(req, res) { + res.status(404); + res.send({ error: "Not found" }); +} + +module.exports = router; diff --git a/src/js/apis/osrm/1.0.0/init.js b/src/js/apis/osrm/1.0.0/init.js new file mode 100644 index 00000000..28f1fc5a --- /dev/null +++ b/src/js/apis/osrm/1.0.0/init.js @@ -0,0 +1,1017 @@ +'use strict'; + +const log4js = require('log4js'); + +var LOGGER = log4js.getLogger("INIT"); + +module.exports = { + + /** + * + * @function + * @name createGetCapabilities + * @description Fonction utilisée pour créer le GetCapabilities + * @param {object} app - App ExpressJS + * @param {string} uid - uid de l'api. Il permet de stocker des objets dans app. + * @return {boolean} True si tout s'est bien passé et False sinon + * + */ + + createGetCapabilities: function(app, uid) { + + // récupération du service + let service = app.get("service"); + + // récupération de la configuration de l'application + let globalConfiguration = service.configuration; + + //création du getCapabilities + let getCapabilities = {}; + + // info + getCapabilities.info = {}; + // info.name + getCapabilities.info.name = globalConfiguration.application.name; + // info.title + getCapabilities.info.title = globalConfiguration.application.title; + // info.description + getCapabilities.info.description = globalConfiguration.application.description; + // info.url + getCapabilities.info.url = globalConfiguration.application.url; + + // provider + getCapabilities.provider = {}; + + if (globalConfiguration.application.provider) { + + // provider.name + getCapabilities.provider.name = globalConfiguration.application.provider.name; + // provider.site + if (globalConfiguration.application.provider.site) { + getCapabilities.provider.site = globalConfiguration.application.provider.site; + } else { + getCapabilities.provider.site = ""; + } + // provider.mail + getCapabilities.provider.mail = globalConfiguration.application.provider.mail; + + } else { + getCapabilities.provider.name = ""; + getCapabilities.provider.site = ""; + getCapabilities.provider.mail = ""; + } + + // api + getCapabilities.api = {}; + // api.name + getCapabilities.api.name = "osrm"; + // api.version + getCapabilities.api.version = "1.0.0"; + + // --- operations + getCapabilities.operations = new Array(); + + // route + + // On vérifie que l'opération route est disponible et on l'intégre seulement si elle est + if (service.verifyAvailabilityOperation("route")) { + + // récupération de l'opération route du service + let serviceOpRoute = service.getOperationById("route"); + + let routeDescription = {}; + // route.id + routeDescription.id = "route"; + // route.description + routeDescription.description = serviceOpRoute.description; + // route.url + routeDescription.url = "/route?"; + // route.methods + routeDescription.methods = new Array(); + routeDescription.methods.push("GET"); + routeDescription.methods.push("POST"); + + // -- route.parameters + routeDescription.parameters = new Array(); + + // TODO: refactorer tout ce code. Une fonction qui prend en argument un paramètre et créer l'objet getCap + // route.parameters.resource + let resourceServiceParameter = serviceOpRoute.getParameterById("resource"); + let resourceParameterDescription = {}; + resourceParameterDescription.name = "resource"; + resourceParameterDescription.in = "query"; + resourceParameterDescription.description = resourceServiceParameter.description; + resourceParameterDescription.required = resourceServiceParameter.required; + resourceParameterDescription.default = resourceServiceParameter.defaultValue; + resourceParameterDescription.schema = {}; + resourceParameterDescription.schema.type = "string"; + resourceParameterDescription.example = "bduni"; + routeDescription.parameters.push(resourceParameterDescription); + + // route.parameters.start + let startServiceParameter = serviceOpRoute.getParameterById("start"); + let startParameterDescription = {}; + startParameterDescription.name = "start"; + startParameterDescription.in = "query"; + startParameterDescription.description = startServiceParameter.description; + startParameterDescription.required = startServiceParameter.required; + startParameterDescription.default = startServiceParameter.defaultValue; + startParameterDescription.schema = {}; + startParameterDescription.schema.type = "string"; + startParameterDescription.example = "2.337306,48.849319"; + routeDescription.parameters.push(startParameterDescription); + + // route.parameters.end + let endServiceParameter = serviceOpRoute.getParameterById("end"); + let endParameterDescription = {}; + endParameterDescription.name = "end"; + endParameterDescription.in = "query"; + endParameterDescription.description = endServiceParameter.description; + endParameterDescription.required = endServiceParameter.required; + endParameterDescription.default = endServiceParameter.defaultValue; + endParameterDescription.schema = {}; + endParameterDescription.schema.type = "string"; + endParameterDescription.example = "2.367776,48.852891"; + routeDescription.parameters.push(endParameterDescription); + + // route.parameters.intermediates + let intermediatesServiceParameter = serviceOpRoute.getParameterById("intermediates"); + let intermediatesParameterDescription = {}; + intermediatesParameterDescription.name = "intermediates"; + intermediatesParameterDescription.in = "query"; + intermediatesParameterDescription.description = intermediatesServiceParameter.description; + intermediatesParameterDescription.required = intermediatesServiceParameter.required; + intermediatesParameterDescription.default = intermediatesServiceParameter.defaultValue; + intermediatesParameterDescription.schema = {}; + intermediatesParameterDescription.schema.type = "array"; + intermediatesParameterDescription.schema.items = {}; + intermediatesParameterDescription.schema.items.type = "string"; + intermediatesParameterDescription.min = intermediatesServiceParameter.min; + intermediatesParameterDescription.max = intermediatesServiceParameter.max; + intermediatesParameterDescription.explode = "false"; + intermediatesParameterDescription.style = "pipeDelimited"; + intermediatesParameterDescription.example = "2.368776,48.852890|2.367976,48.842891"; + routeDescription.parameters.push(intermediatesParameterDescription); + + // route.parameters.profile + let profilesServiceParameter = serviceOpRoute.getParameterById("profile"); + let profileParameterDescription = {}; + profileParameterDescription.name = "profile"; + profileParameterDescription.in = "query"; + profileParameterDescription.description = profilesServiceParameter.description; + profileParameterDescription.required = profilesServiceParameter.required; + profileParameterDescription.default = profilesServiceParameter.defaultValue; + profileParameterDescription.schema = {}; + profileParameterDescription.schema.type = "enumeration"; + profileParameterDescription.example = "car"; + routeDescription.parameters.push(profileParameterDescription); + + // route.parameters.optimization + let optimizationServiceParameter = serviceOpRoute.getParameterById("optimization"); + let optimizationParameterDescription = {}; + optimizationParameterDescription.name = "optimization"; + optimizationParameterDescription.in = "query"; + optimizationParameterDescription.description = optimizationServiceParameter.description; + optimizationParameterDescription.required = optimizationServiceParameter.required; + optimizationParameterDescription.default = optimizationServiceParameter.defaultValue; + optimizationParameterDescription.schema = {}; + optimizationParameterDescription.schema.type = "enumeration"; + optimizationParameterDescription.example = "fastest"; + routeDescription.parameters.push(optimizationParameterDescription); + + // route.parameters.getSteps + let getStepsServiceParameter = serviceOpRoute.getParameterById("getSteps"); + let getStepsParameterDescription = {}; + getStepsParameterDescription.name = "getSteps"; + getStepsParameterDescription.in = "query"; + getStepsParameterDescription.description = getStepsServiceParameter.description; + getStepsParameterDescription.required = getStepsServiceParameter.required; + getStepsParameterDescription.default = getStepsServiceParameter.defaultValue; + getStepsParameterDescription.schema = {}; + getStepsParameterDescription.schema.type = "boolean"; + getStepsParameterDescription.example = "true"; + routeDescription.parameters.push(getStepsParameterDescription); + + // route.parameters.waysAttributes + let waysAttributesServiceParameter = serviceOpRoute.getParameterById("waysAttributes"); + let waysAttributesParameterDescription = {}; + waysAttributesParameterDescription.name = "waysAttributes"; + waysAttributesParameterDescription.in = "query"; + waysAttributesParameterDescription.description = waysAttributesServiceParameter.description; + waysAttributesParameterDescription.required = waysAttributesServiceParameter.required; + waysAttributesParameterDescription.default = waysAttributesServiceParameter.defaultValue; + waysAttributesParameterDescription.schema = {}; + waysAttributesParameterDescription.schema.type = "array"; + waysAttributesParameterDescription.schema.items = {}; + waysAttributesParameterDescription.schema.items.type = "string"; + waysAttributesParameterDescription.min = waysAttributesServiceParameter.min; + waysAttributesParameterDescription.max = waysAttributesServiceParameter.max; + waysAttributesParameterDescription.explode = "false"; + waysAttributesParameterDescription.style = "pipeDelimited"; + waysAttributesParameterDescription.example = "name|type"; + routeDescription.parameters.push(waysAttributesParameterDescription); + + // route.parameters.geometryFormat + let geometryFormatServiceParameter = serviceOpRoute.getParameterById("geometryFormat"); + let geometryFormatParameterDescription = {}; + geometryFormatParameterDescription.name = "geometryFormat"; + geometryFormatParameterDescription.in = "query"; + geometryFormatParameterDescription.description = geometryFormatServiceParameter.description; + geometryFormatParameterDescription.required = geometryFormatServiceParameter.required; + geometryFormatParameterDescription.default = geometryFormatServiceParameter.defaultValue; + geometryFormatParameterDescription.schema = {}; + geometryFormatParameterDescription.schema.type = "enumeration"; + geometryFormatParameterDescription.example = "geojson"; + routeDescription.parameters.push(geometryFormatParameterDescription); + + // route.parameters.getBbox + let getBboxServiceParameter = serviceOpRoute.getParameterById("bbox"); + let getBboxParameterDescription = {}; + getBboxParameterDescription.name = "getBbox"; + getBboxParameterDescription.in = "query"; + getBboxParameterDescription.description = getBboxServiceParameter.description; + getBboxParameterDescription.required = getBboxServiceParameter.required; + getBboxParameterDescription.default = getBboxServiceParameter.defaultValue; + getBboxParameterDescription.schema = {}; + getBboxParameterDescription.schema.type = "boolean"; + getBboxParameterDescription.example = "true"; + routeDescription.parameters.push(getBboxParameterDescription); + + // route.parameters.crs + let projectionServiceParameter = serviceOpRoute.getParameterById("projection"); + let crsParameterDescription = {}; + crsParameterDescription.name = "crs"; + crsParameterDescription.in = "query"; + crsParameterDescription.description = projectionServiceParameter.description; + crsParameterDescription.required = projectionServiceParameter.required; + crsParameterDescription.default = projectionServiceParameter.defaultValue; + crsParameterDescription.schema = {}; + crsParameterDescription.schema.type = "enumeration"; + crsParameterDescription.example = "EPSG:4326"; + routeDescription.parameters.push(crsParameterDescription); + + // route.parameters.timeUnit + let timeUnitServiceParameter = serviceOpRoute.getParameterById("timeUnit"); + let timeUnitParameterDescription = {}; + timeUnitParameterDescription.name = "timeUnit"; + timeUnitParameterDescription.in = "query"; + timeUnitParameterDescription.description = timeUnitServiceParameter.description; + timeUnitParameterDescription.required = timeUnitServiceParameter.required; + timeUnitParameterDescription.default = timeUnitServiceParameter.defaultValue; + timeUnitParameterDescription.schema = {}; + timeUnitParameterDescription.schema.type = "enumeration"; + timeUnitParameterDescription.example = "minute"; + routeDescription.parameters.push(timeUnitParameterDescription); + + // route.parameters.distanceUnit + let distanceUnitServiceParameter = serviceOpRoute.getParameterById("distanceUnit"); + let distanceUnitParameterDescription = {}; + distanceUnitParameterDescription.name = "distanceUnit"; + distanceUnitParameterDescription.in = "query"; + distanceUnitParameterDescription.description = distanceUnitServiceParameter.description; + distanceUnitParameterDescription.required = distanceUnitServiceParameter.required; + distanceUnitParameterDescription.default = distanceUnitServiceParameter.defaultValue; + distanceUnitParameterDescription.schema = {}; + distanceUnitParameterDescription.schema.type = "enumeration"; + distanceUnitParameterDescription.example = "meter"; + routeDescription.parameters.push(distanceUnitParameterDescription); + + // route.parameters.constraints + let constraintsServiceParameter = serviceOpRoute.getParameterById("constraints"); + let constraintsParameterDescription = {}; + constraintsParameterDescription.name = "constraints"; + constraintsParameterDescription.in = "query"; + constraintsParameterDescription.description = constraintsServiceParameter.description; + constraintsParameterDescription.required = constraintsServiceParameter.required; + constraintsParameterDescription.default = constraintsServiceParameter.defaultValue; + constraintsParameterDescription.schema = {}; + constraintsParameterDescription.schema.type = "array"; + constraintsParameterDescription.schema.items = {}; + constraintsParameterDescription.schema.items.type = "object"; + constraintsParameterDescription.schema.items.properties = {}; + constraintsParameterDescription.schema.items.properties.constraintType = {}; + constraintsParameterDescription.schema.items.properties.constraintType.type = "string"; + constraintsParameterDescription.schema.items.properties.key = {}; + constraintsParameterDescription.schema.items.properties.key.type = "string"; + constraintsParameterDescription.schema.items.properties.operator = {}; + constraintsParameterDescription.schema.items.properties.operator.type = "string"; + constraintsParameterDescription.schema.items.properties.value = {}; + constraintsParameterDescription.schema.items.properties.value.type = "string"; + constraintsParameterDescription.min = constraintsServiceParameter.min; + constraintsParameterDescription.max = constraintsServiceParameter.max; + constraintsParameterDescription.explode = "false"; + constraintsParameterDescription.style = "pipeDelimited"; + constraintsParameterDescription.example = "{'constraintType':'banned','key':'ways_type','operator':'=','value':'autoroute'}"; + routeDescription.parameters.push(constraintsParameterDescription); + + // -- end route.parameters + + getCapabilities.operations.push(routeDescription); + + } + // --- end route + + // isochrone + + // On vérifie que l'opération isochrone est disponible et on l'intégre seulement si elle est + if (service.verifyAvailabilityOperation("isochrone")) { + + // récupération de l'opération isochrone du service + let serviceOpIsochrone = service.getOperationById("isochrone"); + + let isochroneDescription = {}; + // isochrone.id + isochroneDescription.id = "isochrone"; + // isochrone.description + isochroneDescription.description = serviceOpIsochrone.description; + // isochrone.url + isochroneDescription.url = "/isochrone?"; + // isochrone.methods + isochroneDescription.methods = new Array(); + isochroneDescription.methods.push("GET"); + isochroneDescription.methods.push("POST"); + + // -- isochrone.parameters + isochroneDescription.parameters = new Array(); + + // isochrone.parameters.resource + let resourceServiceParameter = serviceOpIsochrone.getParameterById("resource"); + let resourceParameterDescription = {}; + resourceParameterDescription.name = "resource"; + resourceParameterDescription.in = "query"; + resourceParameterDescription.description = resourceServiceParameter.description; + resourceParameterDescription.required = resourceServiceParameter.required; + resourceParameterDescription.default = resourceServiceParameter.defaultValue; + resourceParameterDescription.schema = {}; + resourceParameterDescription.schema.type = "string"; + resourceParameterDescription.example = "bduni"; + isochroneDescription.parameters.push(resourceParameterDescription); + + // isochrone.parameters.point + let pointServiceParameter = serviceOpIsochrone.getParameterById("point"); + let pointParameterDescription = {}; + pointParameterDescription.name = "point"; + pointParameterDescription.in = "query"; + pointParameterDescription.description = pointServiceParameter.description; + pointParameterDescription.required = pointServiceParameter.required; + pointParameterDescription.default = pointServiceParameter.defaultValue; + pointParameterDescription.schema = {}; + pointParameterDescription.schema.type = "string"; + pointParameterDescription.example = "2.337306,48.849319"; + isochroneDescription.parameters.push(pointParameterDescription); + + // isochrone.parameters.costType + let costTypeServiceParameter = serviceOpIsochrone.getParameterById("costType"); + let costTypeParameterDescription = {}; + costTypeParameterDescription.name = "costType"; + costTypeParameterDescription.in = "query"; + costTypeParameterDescription.description = costTypeServiceParameter.description; + costTypeParameterDescription.required = costTypeServiceParameter.required; + costTypeParameterDescription.default = costTypeServiceParameter.defaultValue; + costTypeParameterDescription.schema = {}; + costTypeParameterDescription.schema.type = "string"; + costTypeParameterDescription.example = "time"; + isochroneDescription.parameters.push(costTypeParameterDescription); + + // isochrone.parameters.costValue + let costValueServiceParameter = serviceOpIsochrone.getParameterById("costValue"); + let costValueParameterDescription = {}; + costValueParameterDescription.name = "costValue"; + costValueParameterDescription.in = "query"; + costValueParameterDescription.description = costValueServiceParameter.description; + costValueParameterDescription.required = costValueServiceParameter.required; + costValueParameterDescription.default = costValueServiceParameter.defaultValue; + costValueParameterDescription.schema = {}; + costValueParameterDescription.schema.type = "float"; + costValueParameterDescription.example = "100"; + isochroneDescription.parameters.push(costValueParameterDescription); + + // isochrone.parameters.profile + let profileServiceParameter = serviceOpIsochrone.getParameterById("profile"); + let profileParameterDescription = {}; + profileParameterDescription.name = "profile"; + profileParameterDescription.in = "query"; + profileParameterDescription.description = profileServiceParameter.description; + profileParameterDescription.required = profileServiceParameter.required; + profileParameterDescription.default = profileServiceParameter.defaultValue; + profileParameterDescription.schema = {}; + profileParameterDescription.schema.type = "string"; + profileParameterDescription.example = "2.337306,48.849319"; + isochroneDescription.parameters.push(profileParameterDescription); + + // isochrone.parameters.direction + let directionServiceParameter = serviceOpIsochrone.getParameterById("direction"); + let directionParameterDescription = {}; + directionParameterDescription.name = "direction"; + directionParameterDescription.in = "query"; + directionParameterDescription.description = directionServiceParameter.description; + directionParameterDescription.required = directionServiceParameter.required; + directionParameterDescription.default = directionServiceParameter.defaultValue; + directionParameterDescription.schema = {}; + directionParameterDescription.schema.type = "string"; + directionParameterDescription.example = "departure"; + isochroneDescription.parameters.push(directionParameterDescription); + + // isochrone.parameters.crs + let projectionServiceParameter = serviceOpIsochrone.getParameterById("projection"); + let crsParameterDescription = {}; + crsParameterDescription.name = "crs"; + crsParameterDescription.in = "query"; + crsParameterDescription.description = projectionServiceParameter.description; + crsParameterDescription.required = projectionServiceParameter.required; + crsParameterDescription.default = projectionServiceParameter.defaultValue; + crsParameterDescription.schema = {}; + crsParameterDescription.schema.type = "enumeration"; + crsParameterDescription.example = "EPSG:4326"; + isochroneDescription.parameters.push(crsParameterDescription); + + // isochrone.parameters.geometryFormat + let geometryFormatServiceParameter = serviceOpIsochrone.getParameterById("geometryFormat"); + let geometryFormatParameterDescription = {}; + geometryFormatParameterDescription.name = "geometryFormat"; + geometryFormatParameterDescription.in = "query"; + geometryFormatParameterDescription.description = geometryFormatServiceParameter.description; + geometryFormatParameterDescription.required = geometryFormatServiceParameter.required; + geometryFormatParameterDescription.default = geometryFormatServiceParameter.defaultValue; + geometryFormatParameterDescription.schema = {}; + geometryFormatParameterDescription.schema.type = "enumeration"; + geometryFormatParameterDescription.example = "geojson"; + isochroneDescription.parameters.push(geometryFormatParameterDescription); + + // isochrone.parameters.timeUnit + let timeUnitServiceParameter = serviceOpIsochrone.getParameterById("timeUnit"); + let timeUnitParameterDescription = {}; + timeUnitParameterDescription.name = "timeUnit"; + timeUnitParameterDescription.in = "query"; + timeUnitParameterDescription.description = timeUnitServiceParameter.description; + timeUnitParameterDescription.required = timeUnitServiceParameter.required; + timeUnitParameterDescription.default = timeUnitServiceParameter.defaultValue; + timeUnitParameterDescription.schema = {}; + timeUnitParameterDescription.schema.type = "enumeration"; + timeUnitParameterDescription.example = "minute"; + isochroneDescription.parameters.push(timeUnitParameterDescription); + + // isochrone.parameters.distanceUnit + let distanceUnitServiceParameter = serviceOpIsochrone.getParameterById("distanceUnit"); + let distanceUnitParameterDescription = {}; + distanceUnitParameterDescription.name = "distanceUnit"; + distanceUnitParameterDescription.in = "query"; + distanceUnitParameterDescription.description = distanceUnitServiceParameter.description; + distanceUnitParameterDescription.required = distanceUnitServiceParameter.required; + distanceUnitParameterDescription.default = distanceUnitServiceParameter.defaultValue; + distanceUnitParameterDescription.schema = {}; + distanceUnitParameterDescription.schema.type = "enumeration"; + distanceUnitParameterDescription.example = "meter"; + isochroneDescription.parameters.push(distanceUnitParameterDescription); + + // isochrone.parameters.constraints + let constraintsServiceParameter = serviceOpIsochrone.getParameterById("constraints"); + let constraintsParameterDescription = {}; + constraintsParameterDescription.name = "constraints"; + constraintsParameterDescription.in = "query"; + constraintsParameterDescription.description = constraintsServiceParameter.description; + constraintsParameterDescription.required = constraintsServiceParameter.required; + constraintsParameterDescription.default = constraintsServiceParameter.defaultValue; + constraintsParameterDescription.schema = {}; + constraintsParameterDescription.schema.type = "array"; + constraintsParameterDescription.schema.items = {}; + constraintsParameterDescription.schema.items.type = "object"; + constraintsParameterDescription.schema.items.properties = {}; + constraintsParameterDescription.schema.items.properties.constraintType = {}; + constraintsParameterDescription.schema.items.properties.constraintType.type = "string"; + constraintsParameterDescription.schema.items.properties.key = {}; + constraintsParameterDescription.schema.items.properties.key.type = "string"; + constraintsParameterDescription.schema.items.properties.operator = {}; + constraintsParameterDescription.schema.items.properties.operator.type = "string"; + constraintsParameterDescription.schema.items.properties.value = {}; + constraintsParameterDescription.schema.items.properties.value.type = "string"; + constraintsParameterDescription.min = constraintsServiceParameter.min; + constraintsParameterDescription.max = constraintsServiceParameter.max; + constraintsParameterDescription.explode = "false"; + constraintsParameterDescription.style = "pipeDelimited"; + constraintsParameterDescription.example = "{'constraintType':'banned','key':'ways_type','operator':'=','value':'autoroute'}"; + isochroneDescription.parameters.push(constraintsParameterDescription); + + // -- end isochrone.parameters + + getCapabilities.operations.push(isochroneDescription); + + } + // -- end isochrone + + // nearest + + // On vérifie que l'opération nearest est disponible et on l'intégre seulement si elle est + if (service.verifyAvailabilityOperation("nearest")) { + + // récupération de l'opération nearest du service + let serviceOpNearest = service.getOperationById("nearest"); + + let nearestDescription = {}; + // nearest.id + nearestDescription.id = "nearest"; + // nearest.description + nearestDescription.description = serviceOpNearest.description; + // nearest.url + nearestDescription.url = "/nearest?"; + // nearest.methods + nearestDescription.methods = new Array(); + nearestDescription.methods.push("GET"); + nearestDescription.methods.push("POST"); + + // -- nearest.parameters + nearestDescription.parameters = new Array(); + + // TODO: refactorer tout ce code. Une fonction qui prend en argument un paramètre et créer l'objet getCap + // nearest.parameters.resource + let resourceServiceParameter = serviceOpNearest.getParameterById("resource"); + let resourceParameterDescription = {}; + resourceParameterDescription.name = "resource"; + resourceParameterDescription.in = "query"; + resourceParameterDescription.description = resourceServiceParameter.description; + resourceParameterDescription.required = resourceServiceParameter.required; + resourceParameterDescription.default = resourceServiceParameter.defaultValue; + resourceParameterDescription.schema = {}; + resourceParameterDescription.schema.type = "string"; + resourceParameterDescription.example = "bduni"; + nearestDescription.parameters.push(resourceParameterDescription); + + // nearest.parameters.coordinates + let coordinatesServiceParameter = serviceOpNearest.getParameterById("coordinates"); + let coordinatesParameterDescription = {}; + coordinatesParameterDescription.name = "coordinates"; + coordinatesParameterDescription.in = "query"; + coordinatesParameterDescription.description = coordinatesServiceParameter.description; + coordinatesParameterDescription.required = coordinatesServiceParameter.required; + coordinatesParameterDescription.default = coordinatesServiceParameter.defaultValue; + coordinatesParameterDescription.schema = {}; + coordinatesParameterDescription.schema.type = "string"; + coordinatesParameterDescription.example = "2.337306,48.849319"; + nearestDescription.parameters.push(coordinatesParameterDescription); + + // nearest.parameters.number + let numbersServiceParameter = serviceOpNearest.getParameterById("number"); + let numberParameterDescription = {}; + numberParameterDescription.name = "nbPoints"; + numberParameterDescription.in = "query"; + numberParameterDescription.description = numbersServiceParameter.description; + numberParameterDescription.required = numbersServiceParameter.required; + numberParameterDescription.default = numbersServiceParameter.defaultValue; + numberParameterDescription.schema = {}; + numberParameterDescription.schema.type = "integer"; + numberParameterDescription.example = 1; + nearestDescription.parameters.push(numberParameterDescription); + + // nearest.parameters.crs + let projectionServiceParameter = serviceOpNearest.getParameterById("projection"); + let crsParameterDescription = {}; + crsParameterDescription.name = "crs"; + crsParameterDescription.in = "query"; + crsParameterDescription.description = projectionServiceParameter.description; + crsParameterDescription.required = projectionServiceParameter.required; + crsParameterDescription.default = projectionServiceParameter.defaultValue; + crsParameterDescription.schema = {}; + crsParameterDescription.schema.type = "enumeration"; + crsParameterDescription.example = "EPSG:4326"; + nearestDescription.parameters.push(crsParameterDescription); + + + + // -- end nearest.parameters + + getCapabilities.operations.push(nearestDescription); + + } + // --- end nearest + + // --- end operations + + // --- resources + getCapabilities.resources = new Array(); + let resources = service.getResources(); + + for(let resourceId in resources) { + + let resourceDescription = {}; + let localResource = resources[resourceId]; + + // resource.id + resourceDescription.id = localResource.id; + + // resource.description + resourceDescription.description = localResource.configuration.description; + + // -- resource.availableOperations + resourceDescription.availableOperations = new Array(); + + // - route + + // On vérifie que l'opération route est disponible et on l'intégre seulement si elle est + if (service.verifyAvailabilityOperation("route")) { + + // on vérifie qu'elle est disponible sur la ressource + if (localResource.verifyAvailabilityOperation("route")) { + + + // on récupère l'opération de ressource + let resourceOperation = localResource.getOperationById("route"); + + let routeAvailableOperation = {}; + routeAvailableOperation.id = "route"; + routeAvailableOperation.availableParameters = new Array(); + + // route.resource + let resourceParameter = resourceOperation.getParameterById("resource"); + let routeResource = {}; + routeResource.id = "resource"; + routeResource.values = resourceParameter.values; + if (resourceParameter.serviceParameter.defaultValue === "true") { + routeResource.defaultValue = resourceParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeResource); + + // route.start + let startParameter = resourceOperation.getParameterById("start"); + let routeStart = {}; + routeStart.id = "start"; + routeStart.values = startParameter.values; + if (startParameter.serviceParameter.defaultValue === "true") { + routeStart.defaultValue = startParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeStart); + + // route.end + let endParameter = resourceOperation.getParameterById("end"); + let routeEnd = {}; + routeEnd.id = "end"; + routeEnd.values = endParameter.values; + if (endParameter.serviceParameter.defaultValue === "true") { + routeEnd.defaultValue = endParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeEnd); + + // route.intermediates + let intermediatesParameter = resourceOperation.getParameterById("intermediates"); + let routeIntermediates = {}; + routeIntermediates.id = "intermediates"; + routeIntermediates.values = intermediatesParameter.values; + if (intermediatesParameter.serviceParameter.defaultValue === "true") { + routeIntermediates.defaultValue = intermediatesParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeIntermediates); + + // route.profile + let profileParameter = resourceOperation.getParameterById("profile"); + let routeProfile = {}; + routeProfile.id = "profile"; + routeProfile.values = profileParameter.values; + if (profileParameter.serviceParameter.defaultValue === "true") { + routeProfile.defaultValue = profileParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeProfile); + + // route.optimization + let optimizationParameter = resourceOperation.getParameterById("optimization"); + let routeOptimization = {}; + routeOptimization.id = "optimization"; + routeOptimization.values = optimizationParameter.values; + if (optimizationParameter.serviceParameter.defaultValue === "true") { + routeOptimization.defaultValue = optimizationParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeOptimization); + + // route.getSteps + let getStepsParameter = resourceOperation.getParameterById("getSteps"); + let routeGetSteps = {}; + routeGetSteps.id = "getSteps"; + routeGetSteps.values = getStepsParameter.values; + if (getStepsParameter.serviceParameter.defaultValue === "true") { + routeGetSteps.defaultValue = getStepsParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeGetSteps); + + // route.waysAttributes + let waysAttributesParameter = resourceOperation.getParameterById("waysAttributes"); + let routeWaysAttributes = {}; + routeWaysAttributes.id = "waysAttributes"; + routeWaysAttributes.values = waysAttributesParameter.values; + if (waysAttributesParameter.serviceParameter.defaultValue === "true") { + routeWaysAttributes.defaultValue = waysAttributesParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeWaysAttributes); + + // route.geometryFormat + let geometryFormatParameter = resourceOperation.getParameterById("geometryFormat"); + let routeGeometriesFormat = {}; + routeGeometriesFormat.id = "geometryFormat"; + routeGeometriesFormat.values = geometryFormatParameter.values; + if (geometryFormatParameter.serviceParameter.defaultValue === "true") { + routeGeometriesFormat.defaultValue = geometryFormatParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeGeometriesFormat); + + // route.getBbox + let bboxParameter = resourceOperation.getParameterById("bbox"); + let routeGetBbox = {}; + routeGetBbox.id = "getBbox"; + routeGetBbox.values = bboxParameter.values; + if (bboxParameter.serviceParameter.defaultValue === "true") { + routeGetBbox.defaultValue = bboxParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeGetBbox); + + // route.crs + let projectionParameter = resourceOperation.getParameterById("projection"); + let routeCrs = {}; + routeCrs.id = "crs"; + routeCrs.values = projectionParameter.values; + if (projectionParameter.serviceParameter.defaultValue === "true") { + routeCrs.defaultValue = projectionParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeCrs); + + // route.timeUnit + let timeUnitParameter = resourceOperation.getParameterById("timeUnit"); + let routeTimeUnit = {}; + routeTimeUnit.id = "timeUnit"; + routeTimeUnit.values = timeUnitParameter.values; + if (timeUnitParameter.serviceParameter.defaultValue === "true") { + routeTimeUnit.defaultValue = timeUnitParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeTimeUnit); + + // route.distanceUnit + let distanceUnitParameter = resourceOperation.getParameterById("distanceUnit"); + let routeDistanceUnit = {}; + routeDistanceUnit.id = "distanceUnit"; + routeDistanceUnit.values = distanceUnitParameter.values; + if (distanceUnitParameter.serviceParameter.defaultValue === "true") { + routeDistanceUnit.defaultValue = distanceUnitParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeDistanceUnit); + + // route.constraints + let constraintsParameter = resourceOperation.getParameterById("constraints"); + let routeConstraints = {}; + routeConstraints.id = "constraints"; + routeConstraints.values = constraintsParameter.getcapabilities; + if (constraintsParameter.serviceParameter.defaultValue === "true") { + routeConstraints.defaultValue = constraintsParameter.defaultValueContent; + } + routeAvailableOperation.availableParameters.push(routeConstraints); + + resourceDescription.availableOperations.push(routeAvailableOperation); + + } + + } + // - end route + + // - isochrone + + // On vérifie que l'opération isochrone est disponible et on l'intégre seulement si elle est + if (service.verifyAvailabilityOperation("isochrone")) { + + // on vérifie qu'elle est disponible sur la ressource + if (localResource.verifyAvailabilityOperation("isochrone")) { + + + // on récupère l'opération de ressource + let resourceOperation = localResource.getOperationById("isochrone"); + + let isochroneAvailableOperation = {}; + isochroneAvailableOperation.id = "isochrone"; + isochroneAvailableOperation.availableParameters = new Array(); + + // isochrone.resource + let resourceParameter = resourceOperation.getParameterById("resource"); + let isochroneResource = {}; + isochroneResource.id = "resource"; + isochroneResource.values = resourceParameter.values; + if (resourceParameter.serviceParameter.defaultValue === "true") { + isochroneResource.defaultValue = resourceParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneResource); + + // isochrone.point + let pointParameter = resourceOperation.getParameterById("point"); + let isochronePoint = {}; + isochronePoint.id = "point"; + isochronePoint.values = pointParameter.values; + if (pointParameter.serviceParameter.defaultValue === "true") { + isochronePoint.defaultValue = pointParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochronePoint); + + // isochrone.costType + let costTypeParameter = resourceOperation.getParameterById("costType"); + let isochroneCostType = {}; + isochroneCostType.id = "costType"; + isochroneCostType.values = costTypeParameter.values; + if (costTypeParameter.serviceParameter.defaultValue === "true") { + isochroneCostType.defaultValue = costTypeParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneCostType); + + // isochrone.costValue + let costValueParameter = resourceOperation.getParameterById("costValue"); + let isochroneCostValue = {}; + isochroneCostValue.id = "costValue"; + isochroneCostValue.values = costValueParameter.values; + if (costValueParameter.serviceParameter.defaultValue === "true") { + isochroneCostValue.defaultValue = costValueParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneCostValue); + + // isochrone.profile + let profileParameter = resourceOperation.getParameterById("profile"); + let isochroneProfile = {}; + isochroneProfile.id = "profile"; + isochroneProfile.values = profileParameter.values; + if (profileParameter.serviceParameter.defaultValue === "true") { + isochroneProfile.defaultValue = profileParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneProfile); + + // isochrone.direction + let directionParameter = resourceOperation.getParameterById("direction"); + let isochroneDirection = {}; + isochroneDirection.id = "direction"; + isochroneDirection.values = directionParameter.values; + if (directionParameter.serviceParameter.defaultValue === "true") { + isochroneDirection.defaultValue = directionParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneDirection); + + // isochrone.crs + let projectionParameter = resourceOperation.getParameterById("projection"); + let isochroneProjection = {}; + isochroneProjection.id = "projection"; + isochroneProjection.values = projectionParameter.values; + if (projectionParameter.serviceParameter.defaultValue === "true") { + isochroneProjection.defaultValue = projectionParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneProjection); + + + // isochrone.geometryFormat + let geometryFormatParameter = resourceOperation.getParameterById("geometryFormat"); + let isochroneGeometriesFormat = {}; + isochroneGeometriesFormat.id = "geometryFormat"; + isochroneGeometriesFormat.values = geometryFormatParameter.values; + if (geometryFormatParameter.serviceParameter.defaultValue === "true") { + isochroneGeometriesFormat.defaultValue = geometryFormatParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneGeometriesFormat); + + // isochrone.timeUnit + let timeUnitParameter = resourceOperation.getParameterById("timeUnit"); + let isochroneTimeUnit = {}; + isochroneTimeUnit.id = "timeUnit"; + isochroneTimeUnit.values = timeUnitParameter.values; + if (timeUnitParameter.serviceParameter.defaultValue === "true") { + isochroneTimeUnit.defaultValue = timeUnitParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneTimeUnit); + + // isochrone.distanceUnit + let distanceUnitParameter = resourceOperation.getParameterById("distanceUnit"); + let isochroneDistanceUnit = {}; + isochroneDistanceUnit.id = "distanceUnit"; + isochroneDistanceUnit.values = distanceUnitParameter.values; + if (distanceUnitParameter.serviceParameter.defaultValue === "true") { + isochroneDistanceUnit.defaultValue = distanceUnitParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneDistanceUnit); + + // isochrone.constraints + let constraintsParameter = resourceOperation.getParameterById("constraints"); + let isochroneConstraints = {}; + isochroneConstraints.id = "constraints"; + isochroneConstraints.values = constraintsParameter.getcapabilities; + if (constraintsParameter.serviceParameter.defaultValue === "true") { + isochroneConstraints.defaultValue = constraintsParameter.defaultValueContent; + } + isochroneAvailableOperation.availableParameters.push(isochroneConstraints); + + resourceDescription.availableOperations.push(isochroneAvailableOperation); + + } + + } + // - end isochrone + + // - nearest + + // On vérifie que l'opération nearest est disponible et on l'intégre seulement si elle est + if (service.verifyAvailabilityOperation("nearest")) { + + // on vérifie qu'elle est disponible sur la ressource + if (localResource.verifyAvailabilityOperation("nearest")) { + + + // on récupère l'opération de ressource + let resourceOperation = localResource.getOperationById("nearest"); + + let nearestAvailableOperation = {}; + nearestAvailableOperation.id = "nearest"; + nearestAvailableOperation.availableParameters = new Array(); + + // nearest.resource + let resourceParameter = resourceOperation.getParameterById("resource"); + let nearestResource = {}; + nearestResource.id = "resource"; + nearestResource.values = resourceParameter.values; + if (resourceParameter.serviceParameter.defaultValue === "true") { + nearestResource.defaultValue = resourceParameter.defaultValueContent; + } + nearestAvailableOperation.availableParameters.push(nearestResource); + + // nearest.coordinates + let coordinatesParameter = resourceOperation.getParameterById("coordinates"); + let nearestCoordinates = {}; + nearestCoordinates.id = "coordinates"; + nearestCoordinates.values = coordinatesParameter.values; + if (coordinatesParameter.serviceParameter.defaultValue === "true") { + nearestCoordinates.defaultValue = coordinatesParameter.defaultValueContent; + } + nearestAvailableOperation.availableParameters.push(nearestCoordinates); + + // nearest.number + let numberParameter = resourceOperation.getParameterById("number"); + let nearestNumber = {}; + nearestNumber.id = "nbPoints"; + nearestNumber.values = numberParameter.values; + if (numberParameter.serviceParameter.defaultValue === "true") { + nearestNumber.defaultValue = numberParameter.defaultValueContent; + } + nearestAvailableOperation.availableParameters.push(nearestNumber); + + // nearest.crs + let projectionParameter = resourceOperation.getParameterById("projection"); + let nearestCrs = {}; + nearestCrs.id = "crs"; + nearestCrs.values = projectionParameter.values; + if (projectionParameter.serviceParameter.defaultValue === "true") { + nearestCrs.defaultValue = projectionParameter.defaultValueContent; + } + nearestAvailableOperation.availableParameters.push(nearestCrs); + + resourceDescription.availableOperations.push(nearestAvailableOperation); + + } + + } + // - end nearest + + // -- end resource.availableOperations + + + + getCapabilities.resources.push(resourceDescription); + + } // end for(let resourceId in resources) + + // --- end resources + + // sauvegarde du getCapabilities + app.set(uid + "-getcap", getCapabilities); + + return true; + + }, + + /** + * + * @function + * @name run + * @description Fonction lancée avant la mise en service du serveur. + * @param {object} app - App ExpressJS + * @param {string} uid - uid de l'api. Il permet de stocker des objets dans app. + * @return {boolean} True si tout s'est bien passé et False sinon + * + */ + + run: function(app, uid) { + try { + + // TODO: vérification que l'ensemble des opérations et paramètres soient disponibles + // ils sont utilisés dans l'api mais leur existence n'est pas vérifiée + + // Création du GetCapabilities + if (!this.createGetCapabilities(app, uid)) { + LOGGER.error("Erreur lors de la creation du GetCapabilities."); + return false; + } else { + // tout s'est bien passé + } + return true; + + } catch (err) { + LOGGER.error("Erreur lors de la creation du GetCapabilities.", err); + return false; + } + + } + +} diff --git a/src/js/apis/osrm/1.0.0/update.js b/src/js/apis/osrm/1.0.0/update.js new file mode 100644 index 00000000..32cff8d2 --- /dev/null +++ b/src/js/apis/osrm/1.0.0/update.js @@ -0,0 +1,48 @@ +'use strict'; + +const log4js = require('log4js'); + +var LOGGER = log4js.getLogger("INIT"); + +module.exports = { + + /** + * + * @function + * @name updateGetCapabilities + * @description Fonction utilisée pour mettre à jour le GetCapabilities + * @param {object} app - App ExpressJS + * @return {boolean} True si tout s'est bien passé et False sinon + * + */ + + updateGetCapabilities: function(app) { + + return true; + + }, + + /** + * + * @function + * @name run + * @description Fonction lancée lors d'une MAJ sur le serveur. + * @param {object} app - App ExpressJS + * @param {string} uid - uid de l'api. Il permet de stocker des objets dans app. + * @return {boolean} True si tout s'est bien passé et False sinon + * + */ + + run: function(app, uid) { + + // Création du GetCapabilities + if (!this.updateGetCapabilities(app)) { + LOGGER.error("Erreur lors de la creation du GetCapabilities."); + return false; + } + + return true; + + } + +} diff --git a/src/js/apis/simple/1.0.0/update.js b/src/js/apis/simple/1.0.0/update.js index 141acd34..32cff8d2 100644 --- a/src/js/apis/simple/1.0.0/update.js +++ b/src/js/apis/simple/1.0.0/update.js @@ -9,8 +9,8 @@ module.exports = { /** * * @function - * @name createGetCapabilities - * @description Fonction utilisée pour créer le GetCapabilities + * @name updateGetCapabilities + * @description Fonction utilisée pour mettre à jour le GetCapabilities * @param {object} app - App ExpressJS * @return {boolean} True si tout s'est bien passé et False sinon * From 79e132014aefdf322e5a3337eefc8927d5137d31 Mon Sep 17 00:00:00 2001 From: Xav Dmz Date: Mon, 20 Nov 2023 11:38:14 +0100 Subject: [PATCH 2/7] WIP: OSRM native API: routing coordinates validation. --- .../apis/osrm/1.0.0/controller/controller.js | 85 +++++++++++++++++-- 1 file changed, 77 insertions(+), 8 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 89f94547..fe5f1a58 100644 --- a/src/js/apis/osrm/1.0.0/controller/controller.js +++ b/src/js/apis/osrm/1.0.0/controller/controller.js @@ -36,7 +36,8 @@ module.exports = { let profile; let optimization; let tmpStringCoordinates; - let askedProjection; + let coordinatesSequence; + let defaultProjection; LOGGER.debug("checkRouteParameters()"); @@ -66,6 +67,8 @@ module.exports = { // On récupère l'opération route pour faire des vérifications let routeOperation = resource.getOperationById("route"); + defaultProjection = routeOperation.getParameterById("projection").defaultValueContent; + LOGGER.debug("default crs: " + defaultProjection); // Profile and Optimization @@ -108,19 +111,85 @@ module.exports = { } else { LOGGER.debug("raw coordinates:"); LOGGER.debug(parameters.coordinates); -y - let raw_string_pattern = /(-?\d+(\.\d+)?,-?\d+(\.\d+)?;){1,}-?\d+(\.\d+)?,-?\d+(\.\d+)?/; - let polyline_pattern = /polyline\(\S+\)/; - // TODO : extract coordinates in a single format - if (raw_string_pattern.test(parameters.coordinates)) { - - } else if (polyline_pattern.test(parameters.coordinates)) { + let rawStringPattern = /^(-?\d+(\.\d+)?,-?\d+(\.\d+)?;){1,}-?\d+(\.\d+)?,-?\d+(\.\d+)?$/; + let polylinePattern = /^polyline\(\S+\)$/; + // TODO : extract coordinates in a single format + if (rawStringPattern.test(parameters.coordinates)) { + coordinatesSequence = [] + const coordinatesMatchList = parameters.coordinates.matchAll(/(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g) + for (const matchItem in coordinatesMatchList) { + coordinatesSequence.push([parseFloat(matchItem[1]), parseFloat(matchItem[2])]); + } + } else if (polylinePattern.test(parameters.coordinates)) { + coordinatesSequence = polyline.decode(parameters.coordinates); } else { throw errorManager.createError(" Parameter 'coordinates' is invalid: does not match allowed formats", 400); } + LOGGER.debug("coordinates sequence:"); + LOGGER.debug(coordinatesSequence); + + if (coordinatesSequence.length >= 2) { + // Route start point + parameters.start = coordinatesSequence[0].join(","); + LOGGER.debug("user start:"); + LOGGER.debug(parameters.start); + let validity = routeOperation.getParameterById("start").check(parameters.start, defaultProjection); + if (validity.code !== "ok") { + throw errorManager.createError(" Parameter 'start' is invalid: " + validity.message, 400); + } else { + LOGGER.debug("user start valide") + start = new Point(coordinatesSequence[0][0], coordinatesSequence[0][1], defaultProjection); + LOGGER.debug("user start in road2' object:"); + LOGGER.debug(start); + } + validity = null; + + // Route end point + parameters.end = coordinatesSequence[coordinatesSequence.length-1].join(","); + LOGGER.debug("user end:"); + LOGGER.debug(parameters.end); + validity = routeOperation.getParameterById("end").check(parameters.end, askedProjection); + if (validity.code !== "ok") { + throw errorManager.createError(" Parameter 'end' is invalid: " + validity.message, 400); + } else { + LOGGER.debug("user end valide") + end = new Point(coordinatesSequence[coordinatesSequence.length-1][0], coordinatesSequence[coordinatesSequence.length-1][1], defaultProjection); + LOGGER.debug("user end in road2' object:"); + LOGGER.debug(end); + } + } else { + throw errorManager.createError(" Parameter 'coordinates' is invalid: it must contain at least two points"); + } + if (coordinatesSequence.length > 2) { + + LOGGER.debug("user intermediates:"); + LOGGER.debug(coordinatesSequence.slice(1, coordinatesSequence.length-1)); + + let finalIntermediates = ""; + for (let i = 1; i < (coordinatesSequence.length - 2); i++) { + finalIntermediates = finalIntermediates.concat(coordinatesSequence[i].join(","), "|"); + } + finalIntermediates = finalIntermediates.concat(coordinatesSequence[coordinatesSequence.length - 2].join(",")); + + // Vérification de la validité des coordonnées fournies + let validity = routeOperation.getParameterById("intermediates").check(finalIntermediates, defaultProjection); + if (validity.code !== "ok") { + throw errorManager.createError(" Parameter 'coordinates' is invalid: " + validity.message, 400); + } else { + + LOGGER.debug("intermediates valides"); + + if (!routeOperation.getParameterById("intermediates").convertIntoTable(finalIntermediates, routeRequest.intermediates, defaultProjection)) { + throw errorManager.createError(" Parameter 'intermediates' is invalid. Wrong format or out of the bbox. ", 400); + } else { + LOGGER.debug("intermediates in a table:"); + LOGGER.debug(routeRequest.intermediates); + } + } + } } From 85a1e7613208c557090c43da98125f96b1991703 Mon Sep 17 00:00:00 2001 From: Xav Dmz Date: Thu, 11 Jan 2024 17:33:28 +0100 Subject: [PATCH 3/7] WIP: Request parameters implemented when they match routeRequest's parameters, and ignored when not. OSRM controller's writeRouteResponse is being implemnted, but it will need modifications of RouteResponse class. --- documentation/apis/osrm/1.0.0/api.json | 2 +- .../apis/osrm/1.0.0/controller/controller.js | 254 ++++++++++-------- src/js/apis/osrm/1.0.0/index.js | 6 +- 3 files changed, 146 insertions(+), 116 deletions(-) diff --git a/documentation/apis/osrm/1.0.0/api.json b/documentation/apis/osrm/1.0.0/api.json index d2d7a1de..3ccd6078 100644 --- a/documentation/apis/osrm/1.0.0/api.json +++ b/documentation/apis/osrm/1.0.0/api.json @@ -50,7 +50,7 @@ } } }, - "/resource/{resourceId}/{profileId}/{optimizationId}/route/v1/profile/{coordinates}": { + "/{resourceId}/{profileId}/{optimizationId}/route/v1/_/{coordinates}": { "get": { "tags": [ "Route" 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 fe5f1a58..892db8c7 100644 --- a/src/js/apis/osrm/1.0.0/controller/controller.js +++ b/src/js/apis/osrm/1.0.0/controller/controller.js @@ -20,11 +20,11 @@ module.exports = { * * @function * @name checkRouteParameters - * @description Vérification des paramètres d'une requête sur /route - * @param {object} parameters - ensemble des paramètres de la requête - * @param {object} service - Instance de la classe Service - * @param {string} method - Méthode de la requête - * @return {object} RouteRequest - Instance de la classe RouteRequest + * @description Check parameters for a request on /route + * @param {object} parameters - request parameters + * @param {object} service - Service class' instance + * @param {string} method - request method + * @return {object} RouteRequest - RouteRequest class' instance * */ @@ -49,13 +49,13 @@ module.exports = { LOGGER.debug("user resource:"); LOGGER.debug(parameters.resource); - // Vérification de la disponibilité de la ressource et de la compatibilité de son type avec la requête + // Check resource availability, and compatibility between its type and the request if (!service.verifyResourceExistenceById(parameters.resource)) { throw errorManager.createError(" Parameter 'resourceId' is invalid: it does not exist on this service ", 400); } else { resource = service.getResourceById(parameters.resource); - // On vérifie que la ressource peut accepter cette opération + // Check if this operation is allowed for this resource if (!resource.verifyAvailabilityOperation("route")){ throw errorManager.createError(" Operation 'route' is not permitted on this resource ", 400); } else { @@ -65,7 +65,7 @@ module.exports = { } } - // On récupère l'opération route pour faire des vérifications + // Get route operation to check some things let routeOperation = resource.getOperationById("route"); defaultProjection = routeOperation.getParameterById("projection").defaultValueContent; LOGGER.debug("default crs: " + defaultProjection); @@ -79,7 +79,7 @@ module.exports = { } else { LOGGER.debug("user profile:"); LOGGER.debug(parameters.profile); - // Vérification de la validité du paramètre + // Parameter's validity check let validity = routeOperation.getParameterById("profile").check(parameters.profile); if (validity.code !== "ok") { throw errorManager.createError(" Parameter 'profileId' is invalid: " + validity.message, 400); @@ -94,7 +94,7 @@ module.exports = { } else { LOGGER.debug("user optimization:"); LOGGER.debug(parameters.optimization); - // Vérification de la validité du paramètre + // Parameter's validity check let validity = routeOperation.getParameterById("optimization").check(parameters.optimization); if (validity.code !== "ok") { throw errorManager.createError(" Parameter 'optimizationId' is invalid: " + validity.message, 400); @@ -104,7 +104,7 @@ module.exports = { } } - // Paramètres spécifiques à l'API OSRM + // OSRM API specific parameters // coordinates (2 possible formats) if (!parameters.coordinates) { throw errorManager.createError(" Parameter 'coordinates' not found", 400); @@ -162,63 +162,117 @@ module.exports = { } else { throw errorManager.createError(" Parameter 'coordinates' is invalid: it must contain at least two points"); } + } - if (coordinatesSequence.length > 2) { + // Instanciate routeRequest with mandatory parameters + let routeRequest = new RouteRequest(parameters.resource, start, end, profile, optimization); - LOGGER.debug("user intermediates:"); - LOGGER.debug(coordinatesSequence.slice(1, coordinatesSequence.length-1)); + LOGGER.debug(routeRequest); - let finalIntermediates = ""; - for (let i = 1; i < (coordinatesSequence.length - 2); i++) { - finalIntermediates = finalIntermediates.concat(coordinatesSequence[i].join(","), "|"); - } - finalIntermediates = finalIntermediates.concat(coordinatesSequence[coordinatesSequence.length - 2].join(",")); + // Check profile's validity and compatibility with the chosen optimization + if (!resource.checkSourceAvailibilityFromRequest(routeRequest)) { + throw errorManager.createError(" Parameters 'profile' and 'optimization' are not compatible ", 400); + } else { + LOGGER.debug("profile et optimization compatibles"); + } - // Vérification de la validité des coordonnées fournies - let validity = routeOperation.getParameterById("intermediates").check(finalIntermediates, defaultProjection); - if (validity.code !== "ok") { - throw errorManager.createError(" Parameter 'coordinates' is invalid: " + validity.message, 400); - } else { + // Intermediate points + if (coordinatesSequence.length > 2) { - LOGGER.debug("intermediates valides"); + LOGGER.debug("user intermediates:"); + LOGGER.debug(coordinatesSequence.slice(1, coordinatesSequence.length-1)); - if (!routeOperation.getParameterById("intermediates").convertIntoTable(finalIntermediates, routeRequest.intermediates, defaultProjection)) { - throw errorManager.createError(" Parameter 'intermediates' is invalid. Wrong format or out of the bbox. ", 400); - } else { - LOGGER.debug("intermediates in a table:"); - LOGGER.debug(routeRequest.intermediates); - } + let finalIntermediates = ""; + for (let i = 1; i < (coordinatesSequence.length - 2); i++) { + finalIntermediates = finalIntermediates.concat(coordinatesSequence[i].join(","), "|"); + } + finalIntermediates = finalIntermediates.concat(coordinatesSequence[coordinatesSequence.length - 2].join(",")); + + // Check coordinates validity + let validity = routeOperation.getParameterById("intermediates").check(finalIntermediates, defaultProjection); + if (validity.code !== "ok") { + throw errorManager.createError(" Parameter 'coordinates' is invalid: " + validity.message, 400); + } else { + + LOGGER.debug("valid intermediates"); + + if (!routeOperation.getParameterById("intermediates").convertIntoTable(finalIntermediates, routeRequest.intermediates, defaultProjection)) { + throw errorManager.createError(" Parameter 'intermediates' is invalid. Wrong format or out of the bbox. ", 400); + } else { + LOGGER.debug("intermediates in a table:"); + LOGGER.debug(routeRequest.intermediates); } } } + // steps (OSRM) / getSteps (Road2) + if (parameters.steps) { + LOGGER.debug("user getSteps:"); + LOGGER.debug(parameters.steps); - // alternatives + // Check coordinates validity + let validity = routeOperation.getParameterById("getSteps").check(parameters.steps); + if (validity.code !== "ok") { + throw errorManager.createError(" Parameter 'steps' is invalid: " + validity.message, 400); + } else { - // steps + routeRequest.computeSteps = routeOperation.getParameterById("getSteps").specificConvertion(parameters.steps) + if (routeRequest.computeSteps === null) { + throw errorManager.createError(" Parameter 'steps' is invalid ", 400); + } else { + LOGGER.debug("converted getSteps: " + routeRequest.computeSteps); + } - // annotations + } else if ("defaultValueContent" in routeOperation.getParameterById("getSteps") && typeof(routeOperation.getParameterById("getSteps").defaultValueContent) !== "undefined") { + // Default value from configuration + routeRequest.computeSteps = routeOperation.getParameterById("getSteps").defaultValueContent; + LOGGER.debug("configuration default getSteps: " + routeRequest.computeSteps); + } else { + routeRequest.computeSteps = false; + LOGGER.debug("general default getSteps: " + routeRequest.computeSteps); + } - // geometries + // geometries (OSRM) / geometryFormat (Road2) + if (parameters.geometries) { - // overview + LOGGER.debug("user geometryFormat:"); + LOGGER.debug(parameters.geometries); - // continue_straight + // Check coordinates validity + let validity = routeOperation.getParameterById("geometryFormat").check(parameters.geometries); + if (validity.code !== "ok") { + throw errorManager.createError(" Parameter 'geometries' is invalid: " + validity.message, 400); + } else { + LOGGER.debug("geometryFormat valide"); + } + routeRequest.geometryFormat = parameters.geometries; - // On définit la routeRequest avec les paramètres obligatoires - let routeRequest = new RouteRequest(parameters.resource, start, end, profile, optimization); + } else { - LOGGER.debug(routeRequest); + // Default value from configuration + routeRequest.geometryFormat = routeOperation.getParameterById("geometryFormat").defaultValueContent; + LOGGER.debug("default geometryFormat used: " + routeRequest.geometryFormat); - // Vérification de la validité du profile et de sa compatibilité avec l'optimisation - if (!resource.checkSourceAvailibilityFromRequest(routeRequest)) { - throw errorManager.createError(" Parameters 'profile' and 'optimization' are not compatible ", 400); - } else { - LOGGER.debug("profile et optimization compatibles"); } + /* The following OSRM optional parameters are ignored for now, as they don't seem to match any routeRequest property: + - alternatives + - annotations + - bearings + - continue_straight (can maybe be converted to a "constraints" routeRequest property) + - format + - hints + - overview (always "full" in road2) + - radiuses + */ + // optional routeRequest parameters with no OSRM equivalent are ignored or fixed + + routeRequest.bbox = false + routeRequest.distanceUnit = "meter" + routeRequest.timeUnit = "second" + return routeRequest; }, @@ -236,77 +290,53 @@ module.exports = { writeRouteResponse: function(routeRequest, routeResponse, service) { - - }, - - /** - * - * @function - * @name convertPostArrayToGetParameters - * @description Transformation d'un paramètre POST en chaîne de caractères pour avoir l'équivalent d'un paramètre GET. - * @param {object} userParameter - Paramètre POST donné par l'utilisateur - * @param {Parameter} serviceParameter - Instance de la classe Parameter - * @param {string} parameterName - Nom du paramètre converti - * @return {string|array} Paramètre en GET - * - */ - - convertPostArrayToGetParameters: function(userParameter, serviceParameter, parameterName) { - - LOGGER.debug("convertPostArrayToGetParameters() for " + parameterName); - - let finalParameter = ""; - let separator = ""; - - if (serviceParameter.explode === "false") { - if (serviceParameter.style === "pipeDelimited") { - separator = "|"; - LOGGER.debug("separateur trouve pour ce parametre"); - } else { - // ne doit pas arriver - throw errorManager.createError(" Error in parameter configuration. "); - } - } else { - // C'est déjà un tableau qu'on retourne car c'est ce qu'on attend pour le GET - LOGGER.debug("nothing to do for this parameter"); - return userParameter; - } - - if (!Array.isArray(userParameter)) { - throw errorManager.createError(" The parameter " + parameterName + " is not an array. ", 400); - } else { - LOGGER.debug("Le parametre est un tableau"); - } - if (userParameter.length === 0) { - throw errorManager.createError(" The parameter " + parameterName + " is an empty array. ", 400); - } else { - LOGGER.debug("Le parametre est un tableau non vide"); - } - - try { - if (typeof userParameter[0] !== "object") { - finalParameter = userParameter[0]; - } else { - finalParameter = JSON.stringify(userParameter[0]); - } - } catch(err) { - throw errorManager.createError(" The parameter " + parameterName + " can't be converted to a string. ", 400); - } - - for (let i = 1; i < userParameter.length; i++) { - try { - //TODO: vérifier que l'on peut mettre i à la place de 0 - if (typeof userParameter[0] !== "object") { - finalParameter = finalParameter + separator + userParameter[i]; - } else { - finalParameter = finalParameter + separator + JSON.stringify(userParameter[i]); + LOGGER.debug("writeRouteResponse()"); + + let userResponse = { + "code": "", + "routes": [], + "waypoints": [] + }; + for (let route in routeResponse.routes) { + let outputRoute = { + "distance": route.distance, + "duration": route.duration, + "geometry": route.geometry.getGeometryWithFormat(routeRequest.geometryFormat), + "legs": [] + }; + + for (let i = 0; i < route.portions.length; i++) { + let portion = route.portions[i] + let leg = { + "distance": portion.distance, + "duration": portion.duration, + "steps": [] + }; + if (userResponse.waypoints.length < (route.portions.length + 1)) { + let coordsPattern = /^(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)$/; + let waypoint = { + "hint": "", + "distance": NaN, + "name": "", + "location": portion.start.match(coordsPattern).slice(1, 2) + }; + userResponse.waypoints.push(waypoint); + + if (i == (route.portions.length - 1)) { + let finalWaypoint= { + "hint": "", + "distance": NaN, + "name": "", + "location": portion.end.match(coordsPattern).slice(1, 2) + }; + userResponse.waypoints.push(finalWaypoint); + } } - } catch(err) { - throw errorManager.createError(" The parameter " + parameterName + " can't be converted to a string. ", 400); } + + userResponse.routes.push(outputRoute) } - return finalParameter; } diff --git a/src/js/apis/osrm/1.0.0/index.js b/src/js/apis/osrm/1.0.0/index.js index dd449ea3..36945710 100644 --- a/src/js/apis/osrm/1.0.0/index.js +++ b/src/js/apis/osrm/1.0.0/index.js @@ -93,11 +93,11 @@ router.all("/resources", function(req, res) { // Route // Pour effectuer un calcul d'itinéraire -router.route("/resource/:resource/:profile/:optimization/route/v1/profile/:coordinates") +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("requete GET sur /osrm/1.0.0/:resource"); LOGGER.debug(req.originalUrl); // On récupère l'instance de Service pour faire les calculs @@ -137,7 +137,7 @@ router.route("/resource/:resource/:profile/:optimization/route/v1/profile/:coord .post(async function(req, res, next) { - LOGGER.debug("requete POST sur /osrm/1.0.0/resource/"); + 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)); }); From 08cc5c4e166b75d463c1cfb0140f45e2a7c9fc1e Mon Sep 17 00:00:00 2001 From: Xav Dmz Date: Mon, 15 Jan 2024 18:03:24 +0100 Subject: [PATCH 4/7] WIP: implementing response writing for an OSRM native routing request. Slight edition of routeResponse and osrmSource classes to do so. --- .../apis/osrm/1.0.0/controller/controller.js | 71 +++++++++---------- src/js/apis/osrm/1.0.0/index.js | 6 +- src/js/responses/routeResponse.js | 28 +++++++- src/js/sources/osrmSource.js | 42 ++++++++++- 4 files changed, 104 insertions(+), 43 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 892db8c7..cc0fb2c2 100644 --- a/src/js/apis/osrm/1.0.0/controller/controller.js +++ b/src/js/apis/osrm/1.0.0/controller/controller.js @@ -280,11 +280,11 @@ module.exports = { * * @function * @name writeRouteResponse - * @description Ré-écriture de la réponse d'un moteur pour une requête sur /route - * @param {object} RouteRequest - Instance de la classe RouteRequest - * @param {object} RouteResponse - Instance de la classe RouteResponse - * @param {object} service - Instance de la classe Service - * @return {object} userResponse - Réponse envoyée à l'utilisateur + * @description Rewrite engine's response to respond to a /route request + * @param {object} RouteRequest - RouteRequest class instance + * @param {object} RouteResponse - RouteResponse class instance + * @param {object} service - Service class instance + * @return {object} userResponse - Response body to serialize for the user * */ @@ -292,49 +292,44 @@ module.exports = { LOGGER.debug("writeRouteResponse()"); + // Initialize userResponse let userResponse = { - "code": "", - "routes": [], - "waypoints": [] + "code": routeResponse.engineExtras.code }; - for (let route in routeResponse.routes) { - let outputRoute = { - "distance": route.distance, - "duration": route.duration, - "geometry": route.geometry.getGeometryWithFormat(routeRequest.geometryFormat), + + let askedProjection = routeRequest.start.projection; + + // Waypoints + let waypointArray = JSON.parse(JSON.stringify(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++) { + let point = routeResponse.routes[0].portions[i-1].end; + waypointArray[i].location = [point.x, point.y]; + } + userResponse.waypoints = waypointArray; + + 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": [] }; - for (let i = 0; i < route.portions.length; i++) { - let portion = route.portions[i] - let leg = { + let legArray = new Array(); + for (let legIdx = 0; legIdx < route.portions.length; legIdx++) { + let portion = route.portions[legIdx] + legArray = { "distance": portion.distance, "duration": portion.duration, "steps": [] }; - if (userResponse.waypoints.length < (route.portions.length + 1)) { - let coordsPattern = /^(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)$/; - let waypoint = { - "hint": "", - "distance": NaN, - "name": "", - "location": portion.start.match(coordsPattern).slice(1, 2) - }; - userResponse.waypoints.push(waypoint); - - if (i == (route.portions.length - 1)) { - let finalWaypoint= { - "hint": "", - "distance": NaN, - "name": "", - "location": portion.end.match(coordsPattern).slice(1, 2) - }; - userResponse.waypoints.push(finalWaypoint); - } - } } - - userResponse.routes.push(outputRoute) } diff --git a/src/js/apis/osrm/1.0.0/index.js b/src/js/apis/osrm/1.0.0/index.js index 36945710..ca584114 100644 --- a/src/js/apis/osrm/1.0.0/index.js +++ b/src/js/apis/osrm/1.0.0/index.js @@ -126,9 +126,7 @@ router.route("/:resource/:profile/:optimization/route/v1/_/:coordinates") const routeRequest = controller.checkRouteParameters(parameters, service, "GET"); LOGGER.debug(routeRequest); - } catch (error) { - return next(error); - } + } return next(errorManager.createError(" Operation not implemented yet on this service ", 501)); @@ -137,6 +135,8 @@ router.route("/:resource/:profile/:optimization/route/v1/_/:coordinates") .post(async function(req, res, next) { + } 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)); diff --git a/src/js/responses/routeResponse.js b/src/js/responses/routeResponse.js index 5bcea103..53b878ec 100644 --- a/src/js/responses/routeResponse.js +++ b/src/js/responses/routeResponse.js @@ -42,13 +42,16 @@ module.exports = class routeResponse extends Response { // profile this._profile = profile; - // optmization + // optimization this._optimization = optimization; // Itinéraires //Tableau contenant l'ensemble des itinéraires calculés par le moteur this._routes = new Array(); + // Informations spécifiques à un moteur et son API native + this._engineExtras = {}; + } /** @@ -189,4 +192,27 @@ module.exports = class routeResponse extends Response { this._routes = st; } + /** + * + * @function + * @name get engineExtras + * @description Récupérer les propriétés non génériques, spécifiques au moteur et à son API + * + */ + get engineExtras () { + return this._engineExtras; + } + + /** + * + * @function + * @name set engineExtras + * @description Attribuer les propriétés non génériques, spécifiques au moteur et à son API + * @param {object} ee - Dictionnaire de propriétés spécifiques au moteur et à son API + * + */ + set engineExtras (ee) { + this._engineExtras = ee; + } + } diff --git a/src/js/sources/osrmSource.js b/src/js/sources/osrmSource.js index fdad5ead..c5e76cab 100644 --- a/src/js/sources/osrmSource.js +++ b/src/js/sources/osrmSource.js @@ -372,6 +372,11 @@ module.exports = class osrmSource extends Source { let profile; let optimization; let routes = new Array(); + let engineExtras = { + "code": "", + "routes": new Array(), + "waypoints": new Array() + }; // Récupération des paramètres de la requête que l'on veut transmettre dans la réponse // --- @@ -385,6 +390,11 @@ module.exports = class osrmSource extends Source { optimization = routeRequest.optimization; // --- + // Récupération des paramètres de la réponse OSRM pour une éventuelle réponse avec son API native + engineExtras.code = osrmResponse.code; + + // --- + // Lecture de la réponse OSRM // --- @@ -428,6 +438,15 @@ module.exports = class osrmSource extends Source { LOGGER.debug("osrm response has 1 or more routes"); } + for (let sourceWaypoint in osrmResponse.waypoints) { + let nativeWaypoint = { + "hint": sourceWaypoint.hint, + "distance": sourceWaypoint.distance, + "name": sourceWaypoint.name + }; + engineExtras.waypoints.push(nativeWaypoint); + } + // routes // Il peut y avoir plusieurs itinéraires for (let i = 0; i < osrmResponse.routes.length; i++) { @@ -436,6 +455,8 @@ module.exports = class osrmSource extends Source { let portions = new Array(); let currentOsrmRoute = osrmResponse.routes[i]; + engineExtras.routes[i] = {}; + nativeLegs = new Array(); // On commence par créer l'itinéraire avec les attributs obligatoires routes[i] = new Route( new Line(currentOsrmRoute.geometry, "geojson", super.projection) ); @@ -474,13 +495,15 @@ module.exports = class osrmSource extends Source { let legEnd = new Point(osrmResponse.waypoints[j+1].location[0], osrmResponse.waypoints[j+1].location[1], super.projection); if (!legEnd.transform(askedProjection)) { - throw errorManager.createError(" Error during reprojection of leg end in OSRM response. "); + throw errorManager.createError(" Error during reprojection of leg end in OSRM response. "); } else { LOGGER.debug("portion end in asked projection:"); LOGGER.debug(legEnd); } + portions[j] = new Portion(legStart, legEnd); + nativeLegs[j] = {}; // On récupère la distance et la durée portions[j].distance = new Distance(currentOsrmRouteLeg.distance,"meter"); @@ -488,6 +511,7 @@ module.exports = class osrmSource extends Source { // Steps let steps = new Array(); + let nativeSteps = new Array(); // On va associer les étapes à la portion concernée for (let k=0; k < currentOsrmRouteLeg.steps.length; k++) { @@ -524,17 +548,33 @@ module.exports = class osrmSource extends Source { if (currentOsrmRouteStep.maneuver.exit) { steps[k].instruction.exit = currentOsrmRouteStep.maneuver.exit; } + + nativeSteps[k] = {}; + nativeSteps[k].mode = currentOsrmRouteStep.mode; + 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].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. "); + } + } + nativeSteps[k].intersections = nativeIntersections; } portions[j].steps = steps; + nativeLegs[j].steps = nativeSteps; } routes[i].portions = portions; + engineExtras.routes[i].legs = nativeLegs; } routeResponse.routes = routes; + routeResponse.engineExtras = engineExtras: return routeResponse; From d1998ef2965e22951dccd1cde523c47cb0a2ca81 Mon Sep 17 00:00:00 2001 From: Xav Dmz Date: Tue, 16 Jan 2024 16:36:03 +0100 Subject: [PATCH 5/7] 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; From 300c00faccba61d0ee8711d3de854a4c3c56abb2 Mon Sep 17 00:00:00 2001 From: Xav Dmz Date: Wed, 17 Jan 2024 16:39:48 +0100 Subject: [PATCH 6/7] OSRM routing request seems to work. TODO: * check result to see if every property is valid and exact. * adapt getcapabilities to OSRM API capabilities. --- docker/distributions/debian/Dockerfile | 2 +- .../apis/osrm/1.0.0/controller/controller.js | 117 +++++++++++++----- src/js/apis/osrm/1.0.0/index.js | 10 +- src/js/apis/osrm/1.0.0/init.js | 2 +- src/js/apis/osrm/1.0.0/update.js | 2 +- src/js/sources/osrmSource.js | 6 +- 6 files changed, 94 insertions(+), 45 deletions(-) diff --git a/docker/distributions/debian/Dockerfile b/docker/distributions/debian/Dockerfile index eb8dbc8d..07b6feaa 100644 --- a/docker/distributions/debian/Dockerfile +++ b/docker/distributions/debian/Dockerfile @@ -28,7 +28,7 @@ RUN apt-get update && \ libsqlite3-mod-spatialite libzmq3-dev libczmq-dev ### Installation prime-server -COPY --from=build /usr/local/lib/libprime_server.so.0.7.0 /usr/lib/libprime_server.so.0.0.0 +COPY --from=build /usr/local/lib/libprime_server.so.0.7.1 /usr/lib/libprime_server.so.0.0.0 COPY --from=build /usr/local/lib/libprime_server.so.0 /usr/lib/libprime_server.so.0 COPY --from=build /usr/local/lib/libprime_server.so /usr/lib/libprime_server.so 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 977a7315..522d195d 100644 --- a/src/js/apis/osrm/1.0.0/controller/controller.js +++ b/src/js/apis/osrm/1.0.0/controller/controller.js @@ -3,17 +3,15 @@ const log4js = require('log4js'); const polyline = require('@mapbox/polyline'); -const Turf = require('@turf/turf'); -const copyManager = require('../../../../utils/copyManager') +const copyManager = require('../../../../utils/copyManager'); const Distance = require('../../../../geography/distance'); const Duration = require('../../../../time/duration'); const errorManager = require('../../../../utils/errorManager'); -const NearestRequest = require('../../../../requests/nearestRequest'); const Point = require('../../../../geometry/point'); const RouteRequest = require('../../../../requests/routeRequest'); -var LOGGER = log4js.getLogger("CONTROLLER"); +let LOGGER = log4js.getLogger("CONTROLLER"); module.exports = { @@ -36,9 +34,8 @@ module.exports = { let end = {}; let profile; let optimization; - let tmpStringCoordinates; let coordinatesSequence; - let defaultProjection; + let askedProjection; LOGGER.debug("checkRouteParameters()"); @@ -68,8 +65,8 @@ module.exports = { // Get route operation to check some things let routeOperation = resource.getOperationById("route"); - defaultProjection = routeOperation.getParameterById("projection").defaultValueContent; - LOGGER.debug("default crs: " + defaultProjection); + askedProjection = routeOperation.getParameterById("projection").defaultValueContent; + LOGGER.debug("default crs: " + askedProjection); // Profile and Optimization @@ -113,17 +110,22 @@ module.exports = { LOGGER.debug("raw coordinates:"); LOGGER.debug(parameters.coordinates); - let rawStringPattern = /^(-?\d+(\.\d+)?,-?\d+(\.\d+)?;){1,}-?\d+(\.\d+)?,-?\d+(\.\d+)?$/; + let rawStringPattern = /^-?\d+(\.\d+)?,-?\d+(\.\d+)?(;-?\d+(\.\d+)?,-?\d+(\.\d+)?)+$/; let polylinePattern = /^polyline\(\S+\)$/; - // TODO : extract coordinates in a single format if (rawStringPattern.test(parameters.coordinates)) { - coordinatesSequence = [] - const coordinatesMatchList = parameters.coordinates.matchAll(/(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g) - for (const matchItem in coordinatesMatchList) { + LOGGER.debug("coordinates are expressed in list format"); + coordinatesSequence = []; + const coordinatesMatchList = parameters.coordinates.matchAll(/(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g); + LOGGER.debug("coordinates matches list:"); + LOGGER.debug(coordinatesMatchList); + for (const matchItem of coordinatesMatchList) { + LOGGER.debug("coordinates match:"); + LOGGER.debug(matchItem); coordinatesSequence.push([parseFloat(matchItem[1]), parseFloat(matchItem[2])]); } } else if (polylinePattern.test(parameters.coordinates)) { + LOGGER.debug("coordinates are expressed in polyline format"); coordinatesSequence = polyline.decode(parameters.coordinates); } else { throw errorManager.createError(" Parameter 'coordinates' is invalid: does not match allowed formats", 400); @@ -136,12 +138,12 @@ module.exports = { parameters.start = coordinatesSequence[0].join(","); LOGGER.debug("user start:"); LOGGER.debug(parameters.start); - let validity = routeOperation.getParameterById("start").check(parameters.start, defaultProjection); + let validity = routeOperation.getParameterById("start").check(parameters.start, askedProjection); if (validity.code !== "ok") { throw errorManager.createError(" Parameter 'start' is invalid: " + validity.message, 400); } else { LOGGER.debug("user start valide") - start = new Point(coordinatesSequence[0][0], coordinatesSequence[0][1], defaultProjection); + start = new Point(coordinatesSequence[0][0], coordinatesSequence[0][1], askedProjection); LOGGER.debug("user start in road2' object:"); LOGGER.debug(start); } @@ -156,7 +158,7 @@ module.exports = { throw errorManager.createError(" Parameter 'end' is invalid: " + validity.message, 400); } else { LOGGER.debug("user end valide") - end = new Point(coordinatesSequence[coordinatesSequence.length-1][0], coordinatesSequence[coordinatesSequence.length-1][1], defaultProjection); + end = new Point(coordinatesSequence[coordinatesSequence.length-1][0], coordinatesSequence[coordinatesSequence.length-1][1], askedProjection); LOGGER.debug("user end in road2' object:"); LOGGER.debug(end); } @@ -190,14 +192,14 @@ module.exports = { finalIntermediates = finalIntermediates.concat(coordinatesSequence[coordinatesSequence.length - 2].join(",")); // Check coordinates validity - let validity = routeOperation.getParameterById("intermediates").check(finalIntermediates, defaultProjection); + let validity = routeOperation.getParameterById("intermediates").check(finalIntermediates, askedProjection); if (validity.code !== "ok") { throw errorManager.createError(" Parameter 'coordinates' is invalid: " + validity.message, 400); } else { LOGGER.debug("valid intermediates"); - if (!routeOperation.getParameterById("intermediates").convertIntoTable(finalIntermediates, routeRequest.intermediates, defaultProjection)) { + if (!routeOperation.getParameterById("intermediates").convertIntoTable(finalIntermediates, routeRequest.intermediates, askedProjection)) { throw errorManager.createError(" Parameter 'intermediates' is invalid. Wrong format or out of the bbox. ", 400); } else { LOGGER.debug("intermediates in a table:"); @@ -223,7 +225,7 @@ module.exports = { } else { LOGGER.debug("converted getSteps: " + routeRequest.computeSteps); } - + } } else if ("defaultValueContent" in routeOperation.getParameterById("getSteps") && typeof(routeOperation.getParameterById("getSteps").defaultValueContent) !== "undefined") { // Default value from configuration routeRequest.computeSteps = routeOperation.getParameterById("getSteps").defaultValueContent; @@ -297,7 +299,8 @@ module.exports = { let userResponse = { "code": routeResponse.engineExtras.code }; - let askedProjection = routeRequest.start.projection; + LOGGER.debug("Engine response code :"); + LOGGER.debug(userResponse.code); // Waypoints let waypointArray = copyManager.deepCopy(routeResponse.engineExtras.waypoints); @@ -308,23 +311,58 @@ module.exports = { waypointArray[i].location = [point.x, point.y]; } userResponse.waypoints = waypointArray; + LOGGER.debug("Waypoints :"); + LOGGER.debug(userResponse.waypoints); // Routes let routeArray = new Array(); for (let routeIdx = 0; routeIdx < routeResponse.routes.length; routeIdx++) { let simpleRoute = routeResponse.routes[routeIdx]; // from road2 standard response - let extraRoute = routeResponse.engineExtras.routes[routeIdx]; // from engine specific extras + let extraRoute = routeResponse.engineExtras.routes[routeIdx]; // from engine specific extrasz // both sources will be fused to craft a response compliant with OSRM's official API definition + // Geometry + routeArray[routeIdx] = {}; + routeArray[routeIdx].geometry = simpleRoute.geometry.getGeometryWithFormat(routeRequest.geometryFormat); + LOGGER.debug("Route " + routeIdx + "'s geometry: " + userResponse.geometry); + + // Distance + if (!simpleRoute.distance.convert(routeRequest.distanceUnit)) { + throw errorManager.createError(" Error during conversion of route distance in response. ", 400); + } else { + routeArray[routeIdx].distance = simpleRoute.distance.value; + } + + // Duration + if (!simpleRoute.duration.convert(routeRequest.timeUnit)) { + throw errorManager.createError(" Error during conversion of route duration in response. ", 400); + } else { + routeArray[routeIdx].duration = simpleRoute.duration.value; + } + // Legs (Road2's "portions") let legArray = new Array(); for (let legIdx = 0; legIdx < simpleRoute.portions.length; legIdx++) { + LOGGER.debug("Computing leg " + legIdx + " of route " + routeIdx); 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 - }; + legArray[legIdx] = {}; + + // Distance + if (!portion.distance.convert(routeRequest.distanceUnit)) { + LOGGER.debug("error during distance conversion: distance " + portion.distance); + throw errorManager.createError(" Error during convertion of portion distance in response. ", 400); + } else { + legArray[legIdx].distance = portion.distance.value; + } + + // Duration + if (!portion.duration.convert(routeRequest.timeUnit)) { + LOGGER.debug("error during duration conversion: duration " + portion.duration); + throw errorManager.createError(" Error during convertion of portion duration in response. ", 400); + } else { + legArray[legIdx].duration = portion.duration.value; + } // Steps (optional) let stepArray = new Array(); @@ -332,17 +370,33 @@ module.exports = { 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 + "type": simpleStep.instruction.type }, "mode": extraStep.mode, "name": simpleStep.name }; + + // Distance + if (!simpleStep.distance.convert(routeRequest.distanceUnit)) { + LOGGER.debug("error during distance conversion: distance " + simpleStep.distance); + throw errorManager.createError(" Error during convertion of portion distance in response. ", 400); + } else { + stepArray[stepIdx].distance = simpleStep.distance.value; + } + + // Duration + if (!simpleStep.duration.convert(routeRequest.timeUnit)) { + LOGGER.debug("error during duration conversion: duration " + simpleStep.duration); + throw errorManager.createError(" Error during convertion of portion duration in response. ", 400); + } else { + stepArray[stepIdx].duration = simpleStep.duration.value; + } + if (simpleStep.instruction.modifier) { stepArray[stepIdx].maneuver.modifier = simpleStep.instruction.modifier; } @@ -356,12 +410,7 @@ module.exports = { } } - routeArray[routeIdx] = { - "distance": simpleRoute.distance, - "duration": simpleRoute.duration, - "geometry": simpleRoute.geometry.getGeometryWithFormat(routeRequest.geometryFormat), - "legs": legArray - }; + routeArray[routeIdx].legs = legArray; } // Finalze userResponse diff --git a/src/js/apis/osrm/1.0.0/index.js b/src/js/apis/osrm/1.0.0/index.js index eda1f6b7..f089c890 100644 --- a/src/js/apis/osrm/1.0.0/index.js +++ b/src/js/apis/osrm/1.0.0/index.js @@ -8,8 +8,8 @@ const controller = require('./controller/controller'); const errorManager = require('../../../utils/errorManager'); const swaggerUi = require('swagger-ui-express'); -var LOGGER = log4js.getLogger("OSRM"); -var router = express.Router(); +let LOGGER = log4js.getLogger("OSRM"); +let router = express.Router(); // API entrypoint router.all("/", function(req, res) { @@ -19,9 +19,9 @@ router.all("/", function(req, res) { // swagger-ui -var apiJsonPath = path.join(__dirname, '..', '..', '..','..','..', 'documentation','apis','osrm', '1.0.0', 'api.json'); +let apiJsonPath = path.join(__dirname, '..', '..', '..','..','..', 'documentation','apis','osrm', '1.0.0', 'api.json'); LOGGER.info("using file '"+ apiJsonPath + "' to initialize swagger-ui for OSRM API version 1.0.0"); -var swaggerDocument = require(apiJsonPath); +let swaggerDocument = require(apiJsonPath); router.use('/openapi', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); // GetCapabilities @@ -43,7 +43,7 @@ router.all("/resources", function(req, res) { // 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) { - let regexpHost = /^http[s]?:\/\/[\w\d:-_\.]*\//; + let regexpHost = /^https?:\/\/[\w\d:-_.]*\//; try { getCapabilities.info.url = getCapabilities.info.url.replace(regexpHost, req.protocol + "://" + req.hostname + "/"); } catch(error) { diff --git a/src/js/apis/osrm/1.0.0/init.js b/src/js/apis/osrm/1.0.0/init.js index 28f1fc5a..4eb68f2b 100644 --- a/src/js/apis/osrm/1.0.0/init.js +++ b/src/js/apis/osrm/1.0.0/init.js @@ -2,7 +2,7 @@ const log4js = require('log4js'); -var LOGGER = log4js.getLogger("INIT"); +let LOGGER = log4js.getLogger("INIT"); module.exports = { diff --git a/src/js/apis/osrm/1.0.0/update.js b/src/js/apis/osrm/1.0.0/update.js index 32cff8d2..b367a955 100644 --- a/src/js/apis/osrm/1.0.0/update.js +++ b/src/js/apis/osrm/1.0.0/update.js @@ -2,7 +2,7 @@ const log4js = require('log4js'); -var LOGGER = log4js.getLogger("INIT"); +let LOGGER = log4js.getLogger("INIT"); module.exports = { diff --git a/src/js/sources/osrmSource.js b/src/js/sources/osrmSource.js index d7f48bd2..136f9dca 100644 --- a/src/js/sources/osrmSource.js +++ b/src/js/sources/osrmSource.js @@ -10,7 +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 copyManager = require('../utils/copyManager') const errorManager = require('../utils/errorManager'); const log4js = require('log4js'); @@ -457,7 +457,7 @@ module.exports = class osrmSource extends Source { let portions = new Array(); let currentOsrmRoute = osrmResponse.routes[i]; engineExtras.routes[i] = {}; - nativeLegs = new Array(); + let nativeLegs = new Array(); // On commence par créer l'itinéraire avec les attributs obligatoires routes[i] = new Route( new Line(currentOsrmRoute.geometry, "geojson", super.projection) ); @@ -552,7 +552,7 @@ module.exports = class osrmSource extends Source { nativeSteps[k] = {}; nativeSteps[k].mode = currentOsrmRouteStep.mode; - nativeIntersections = new Array(); + let nativeIntersections = new Array(); for (let intersectionIndex = 0; intersectionIndex < currentOsrmRouteStep.intersections.length; intersectionIndex++) { let currentIntersection = currentOsrmRouteStep.intersections[intersectionIndex]; nativeIntersections[intersectionIndex] = copyManager.deepCopy(currentIntersection); From d9d931b9cd442b4ddeea7370cff3ed5b97942016 Mon Sep 17 00:00:00 2001 From: Xav Dmz Date: Thu, 18 Jan 2024 16:16:16 +0100 Subject: [PATCH 7/7] Fixed OSRM native response's waypoints, and intersections geometry format. --- .../apis/osrm/1.0.0/controller/controller.js | 2 +- src/js/sources/osrmSource.js | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 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 522d195d..56dcfdff 100644 --- a/src/js/apis/osrm/1.0.0/controller/controller.js +++ b/src/js/apis/osrm/1.0.0/controller/controller.js @@ -419,4 +419,4 @@ module.exports = { return userResponse; } -} \ No newline at end of file +} diff --git a/src/js/sources/osrmSource.js b/src/js/sources/osrmSource.js index 136f9dca..d2575935 100644 --- a/src/js/sources/osrmSource.js +++ b/src/js/sources/osrmSource.js @@ -439,14 +439,15 @@ module.exports = class osrmSource extends Source { LOGGER.debug("osrm response has 1 or more routes"); } - for (let sourceWaypoint in osrmResponse.waypoints) { - let nativeWaypoint = { - "hint": sourceWaypoint.hint, - "distance": sourceWaypoint.distance, - "name": sourceWaypoint.name + for (let waypointIdx = 0; waypointIdx < osrmResponse.waypoints.length; waypointIdx++) { + engineExtras.waypoints[waypointIdx] = { + "hint": osrmResponse.waypoints[waypointIdx].hint, + "distance": osrmResponse.waypoints[waypointIdx].distance, + "name": osrmResponse.waypoints[waypointIdx].name }; - engineExtras.waypoints.push(nativeWaypoint); } + LOGGER.debug("OSRM engineExtras waypoints (before adding to routeResponse:"); + LOGGER.debug(engineExtras.waypoints); // routes // Il peut y avoir plusieurs itinéraires @@ -556,10 +557,11 @@ module.exports = class osrmSource extends Source { for (let intersectionIndex = 0; intersectionIndex < currentOsrmRouteStep.intersections.length; intersectionIndex++) { let currentIntersection = currentOsrmRouteStep.intersections[intersectionIndex]; nativeIntersections[intersectionIndex] = copyManager.deepCopy(currentIntersection); - nativeIntersections[intersectionIndex].location = new Point(currentIntersection.location[0], currentIntersection.location[1], super.projection) - if (!nativeIntersections[intersectionIndex].location.transform(askedProjection)) { + let location = new Point(currentIntersection.location[0], currentIntersection.location[1], super.projection); + if (!location.transform(askedProjection)) { throw errorManager.createError(" Error during reprojection of intersection in OSRM response. "); } + nativeIntersections[intersectionIndex].location = [location.x, location.y]; } nativeSteps[k].intersections = nativeIntersections; }