| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | |
| 25 | |
| 26 | |
| 27 | |
| 28 | |
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
| 34 | |
| 35 | |
| 36 | |
| 37 | import { ZERO, ONE, NOT_FOUND, HTTP_STATUS_OK } from '../main/Constants.js'; |
| 38 | |
| 39 | |
| 40 | |
| 41 | |
| 42 | |
| 43 | const OUR_OSRM_LANGUAGES = [ |
| 44 | 'ar', |
| 45 | 'da', |
| 46 | 'de', |
| 47 | 'en', |
| 48 | 'eo', |
| 49 | 'es', |
| 50 | 'es-ES', |
| 51 | 'fi', |
| 52 | 'fr', |
| 53 | 'he', |
| 54 | 'hu', |
| 55 | 'id', |
| 56 | 'it', |
| 57 | 'ja', |
| 58 | 'ko', |
| 59 | 'my', |
| 60 | 'nl', |
| 61 | 'no', |
| 62 | 'pl', |
| 63 | 'pt-BR', |
| 64 | 'pt-PT', |
| 65 | 'ro', |
| 66 | 'ru', |
| 67 | 'sl', |
| 68 | 'sv', |
| 69 | 'tr', |
| 70 | 'uk', |
| 71 | 'vi', |
| 72 | 'yo', |
| 73 | 'zh-Hans' |
| 74 | ]; |
| 75 | |
| 76 | |
| 77 | |
| 78 | |
| 79 | |
| 80 | const OUR_VERSION = 'v5'; |
| 81 | |
| 82 | |
| 83 | |
| 84 | |
| 85 | |
| 86 | const languages = |
| 87 | { |
| 88 | supportedCodes : [ ], |
| 89 | instructions : {}, |
| 90 | grammars : {}, |
| 91 | abbreviations : {} |
| 92 | }; |
| 93 | |
| 94 | |
| 95 | |
| 96 | |
| 97 | |
| 98 | |
| 99 | |
| 100 | const instructions = languages.instructions; |
| 101 | |
| 102 | |
| 103 | |
| 104 | |
| 105 | |
| 106 | const grammars = languages.grammars; |
| 107 | |
| 108 | |
| 109 | |
| 110 | |
| 111 | |
| 112 | const abbreviations = languages.abbreviations; |
| 113 | |
| 114 | |
| 115 | |
| 116 | |
| 117 | |
| 118 | |
| 119 | |
| 120 | |
| 121 | |
| 122 | |
| 123 | |
| 124 | class OsrmTextInstructions { |
| 125 | |
| 126 | |
| 127 | |
| 128 | |
| 129 | |
| 130 | #directionFromDegree ( language, degree ) { |
| 131 | const NNE = 20; |
| 132 | const ENE = 70; |
| 133 | const ESE = 110; |
| 134 | const SSE = 160; |
| 135 | const SSW = 200; |
| 136 | const WSW = 250; |
| 137 | const WNW = 290; |
| 138 | const NNW = 340; |
| 139 | const NORTH = 360; |
| 140 | |
| 141 | if ( ! degree && ZERO !== degree ) { |
| 142 | return ''; |
| 143 | } |
| 144 | else if ( ZERO > degree && NORTH < degree ) { |
| 145 | throw new Error ( 'Degree ' + degree + ' invalid' ); |
| 146 | } |
| 147 | else if ( NNE >= degree ) { |
| 148 | return instructions[ language ][ OUR_VERSION ].constants.direction.north; |
| 149 | } |
| 150 | else if ( ENE > degree ) { |
| 151 | return instructions[ language ][ OUR_VERSION ].constants.direction.northeast; |
| 152 | } |
| 153 | else if ( ESE >= degree ) { |
| 154 | return instructions[ language ][ OUR_VERSION ].constants.direction.east; |
| 155 | } |
| 156 | else if ( SSE > degree ) { |
| 157 | return instructions[ language ][ OUR_VERSION ].constants.direction.southeast; |
| 158 | } |
| 159 | else if ( SSW >= degree ) { |
| 160 | return instructions[ language ][ OUR_VERSION ].constants.direction.south; |
| 161 | } |
| 162 | else if ( WSW > degree ) { |
| 163 | return instructions[ language ][ OUR_VERSION ].constants.direction.southwest; |
| 164 | } |
| 165 | else if ( WNW >= degree ) { |
| 166 | return instructions[ language ][ OUR_VERSION ].constants.direction.west; |
| 167 | } |
| 168 | else if ( NNW > degree ) { |
| 169 | return instructions[ language ][ OUR_VERSION ].constants.direction.northwest; |
| 170 | } |
| 171 | else { |
| 172 | return instructions[ language ][ OUR_VERSION ].constants.direction.north; |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | async #fetchJson ( data, lngCode ) { |
| 177 | const response = await fetch ( 'TravelNotesProviders/languages/' + data + '/' + lngCode + '.json' ); |
| 178 | if ( HTTP_STATUS_OK === response.status && response.ok ) { |
| 179 | const result = await response.json ( ); |
| 180 | languages [ data ] [ lngCode ] = result; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | |
| 185 | |
| 186 | |
| 187 | |
| 188 | constructor ( ) { |
| 189 | this.abbreviations = abbreviations; |
| 190 | Object.freeze ( this ); |
| 191 | } |
| 192 | |
| 193 | loadLanguage ( lng ) { |
| 194 | const language = NOT_FOUND === OUR_OSRM_LANGUAGES.indexOf ( lng ) ? 'en' : lng; |
| 195 | [ 'instructions', 'grammars', 'abbreviations' ].forEach ( |
| 196 | data => this.#fetchJson ( data, language ) |
| 197 | ); |
| 198 | return language; |
| 199 | } |
| 200 | |
| 201 | capitalizeFirstLetter ( language, string ) { |
| 202 | return string.charAt ( ZERO ).toLocaleUpperCase ( language ) + string.slice ( ONE ); |
| 203 | } |
| 204 | |
| 205 | ordinalize ( language, number ) { |
| 206 | return instructions[ language ][ OUR_VERSION ].constants.ordinalize[ number.toString () ] || ''; |
| 207 | } |
| 208 | |
| 209 | directionFromDegree ( language, degree ) { |
| 210 | return this.#directionFromDegree ( language, degree ); |
| 211 | } |
| 212 | |
| 213 | laneConfig ( step ) { |
| 214 | if ( ! step.intersections || ! step.intersections[ ZERO ].lanes ) { |
| 215 | throw new Error ( 'No lanes object' ); |
| 216 | } |
| 217 | const config = []; |
| 218 | let currentLaneValidity = null; |
| 219 | step.intersections[ ZERO ].lanes.forEach ( function ( lane ) { |
| 220 | if ( null === currentLaneValidity || currentLaneValidity !== lane.valid ) { |
| 221 | if ( lane.valid ) { |
| 222 | config.push ( 'o' ); |
| 223 | } |
| 224 | else { |
| 225 | config.push ( 'x' ); |
| 226 | } |
| 227 | currentLaneValidity = lane.valid; |
| 228 | } |
| 229 | } ); |
| 230 | return config.join ( '' ); |
| 231 | } |
| 232 | |
| 233 | |
| 234 | getWayName ( language, step, options ) { |
| 235 | const classes = options ? options.classes || [] : []; |
| 236 | if ( 'object' !== typeof step ) { |
| 237 | throw new Error ( 'step must be an Object' ); |
| 238 | } |
| 239 | if ( ! Array.isArray ( classes ) ) { |
| 240 | throw new Error ( 'classes must be an Array or undefined' ); |
| 241 | } |
| 242 | let wayName = ''; |
| 243 | let stepName = step.name || ''; |
| 244 | const ref = ( step.ref || '' ).split ( ';' )[ ZERO ]; |
| 245 | if ( stepName === step.ref ) { |
| 246 | stepName = ''; |
| 247 | } |
| 248 | stepName = stepName.replace ( ' (' + step.ref + ')', '' ); |
| 249 | const wayMotorway = NOT_FOUND !== classes.indexOf ( 'motorway' ); |
| 250 | if ( stepName && ref && stepName !== ref && ! wayMotorway ) { |
| 251 | const phrase = instructions[ language ][ OUR_VERSION ].phrase[ 'name and ref' ] || |
| 252 | instructions.en[ OUR_VERSION ].phrase[ 'name and ref' ]; |
| 253 | wayName = this.tokenize ( language, phrase, { |
| 254 | name : stepName, |
| 255 | ref : ref |
| 256 | }, options ); |
| 257 | } |
| 258 | else if ( stepName && ref && wayMotorway && ( /\d/ ).test ( ref ) ) { |
| 259 | wayName = options && options.formatToken ? options.formatToken ( 'ref', ref ) : ref; |
| 260 | } |
| 261 | else if ( ! stepName && ref ) { |
| 262 | wayName = options && options.formatToken ? options.formatToken ( 'ref', ref ) : ref; |
| 263 | } |
| 264 | else { |
| 265 | wayName = options && options.formatToken ? options.formatToken ( 'name', stepName ) : stepName; |
| 266 | } |
| 267 | return wayName; |
| 268 | } |
| 269 | |
| 270 | |
| 271 | compile ( language, step, opts ) { |
| 272 | if ( ! step.maneuver ) { |
| 273 | throw new Error ( 'No step maneuver provided' ); |
| 274 | } |
| 275 | const options = opts || {}; |
| 276 | let type = step.maneuver.type; |
| 277 | const modifier = step.maneuver.modifier; |
| 278 | const mode = step.mode; |
| 279 | const side = step.driving_side; |
| 280 | if ( ! type ) { |
| 281 | throw new Error ( 'Missing step maneuver type' ); |
| 282 | } |
| 283 | if ( 'depart' !== type && 'arrive' !== type && ! modifier ) { |
| 284 | throw new Error ( 'Missing step maneuver modifier' ); |
| 285 | } |
| 286 | if ( ! instructions[ language ][ OUR_VERSION ][ type ] ) { |
| 287 | |
| 288 | console.log ( 'Encountered unknown instruction type: ' + type ); |
| 289 | type = 'turn'; |
| 290 | } |
| 291 | let instructionObject = null; |
| 292 | if ( instructions[ language ][ OUR_VERSION ].modes[ mode ] ) { |
| 293 | instructionObject = instructions[ language ][ OUR_VERSION ].modes[ mode ]; |
| 294 | } |
| 295 | else { |
| 296 | const omitSide = 'off ramp' === type && ZERO <= modifier.indexOf ( side ); |
| 297 | if ( instructions[ language ][ OUR_VERSION ][ type ][ modifier ] && ! omitSide ) { |
| 298 | instructionObject = instructions[ language ][ OUR_VERSION ][ type ][ modifier ]; |
| 299 | } |
| 300 | else { |
| 301 | instructionObject = instructions[ language ][ OUR_VERSION ][ type ].default; |
| 302 | } |
| 303 | } |
| 304 | let laneInstruction = null; |
| 305 | switch ( type ) { |
| 306 | case 'use lane' : |
| 307 | laneInstruction = instructions[ language ][ OUR_VERSION ].constants.lanes[ this.laneConfig ( step ) ]; |
| 308 | if ( ! laneInstruction ) { |
| 309 | instructionObject = instructions[ language ][ OUR_VERSION ][ 'use lane' ].no_lanes; |
| 310 | } |
| 311 | break; |
| 312 | case 'rotary' : |
| 313 | case 'roundabout' : |
| 314 | if ( step.rotary_name && step.maneuver.exit && instructionObject.name_exit ) { |
| 315 | instructionObject = instructionObject.name_exit; |
| 316 | } |
| 317 | else if ( step.rotary_name && instructionObject.name ) { |
| 318 | instructionObject = instructionObject.name; |
| 319 | } |
| 320 | else if ( step.maneuver.exit && instructionObject.exit ) { |
| 321 | instructionObject = instructionObject.exit; |
| 322 | } |
| 323 | else { |
| 324 | instructionObject = instructionObject.default; |
| 325 | } |
| 326 | break; |
| 327 | default : |
| 328 | } |
| 329 | const wayName = this.getWayName ( language, step, options ); |
| 330 | let instruction = instructionObject.default; |
| 331 | if ( step.destinations && step.exits && instructionObject.exit_destination ) { |
| 332 | instruction = instructionObject.exit_destination; |
| 333 | } |
| 334 | else if ( step.destinations && instructionObject.destination ) { |
| 335 | instruction = instructionObject.destination; |
| 336 | } |
| 337 | else if ( step.exits && instructionObject.exit ) { |
| 338 | instruction = instructionObject.exit; |
| 339 | } |
| 340 | else if ( wayName && instructionObject.name ) { |
| 341 | instruction = instructionObject.name; |
| 342 | } |
| 343 | else if ( options.waypointName && instructionObject.named ) { |
| 344 | instruction = instructionObject.named; |
| 345 | } |
| 346 | const destinations = step.destinations && step.destinations.split ( ': ' ); |
| 347 | const destinationRef = destinations && destinations[ ZERO ].split ( ',' )[ ZERO ]; |
| 348 | const destination = destinations && destinations[ ONE ] && destinations[ ONE ].split ( ',' )[ ZERO ]; |
| 349 | let firstDestination = ''; |
| 350 | if ( destination && destinationRef ) { |
| 351 | firstDestination = destinationRef + ': ' + destination; |
| 352 | } |
| 353 | else { |
| 354 | firstDestination = destinationRef || destination || ''; |
| 355 | } |
| 356 | |
| 357 | const nthWaypoint = |
| 358 | ZERO <= options.legIndex && options.legIndex !== options.legCount - ONE |
| 359 | ? |
| 360 | this.ordinalize ( language, options.legIndex + ONE ) |
| 361 | : |
| 362 | ''; |
| 363 | const replaceTokens = { |
| 364 | |
| 365 | way_name : wayName, |
| 366 | destination : firstDestination, |
| 367 | exit : ( step.exits || '' ).split ( ';' )[ ZERO ], |
| 368 | |
| 369 | exit_number : this.ordinalize ( language, step.maneuver.exit || ONE ), |
| 370 | |
| 371 | rotary_name : step.rotary_name, |
| 372 | |
| 373 | lane_instruction : laneInstruction, |
| 374 | modifier : instructions[ language ][ OUR_VERSION ].constants.modifier[ modifier ], |
| 375 | direction : this.directionFromDegree ( language, step.maneuver.bearing_after ), |
| 376 | nth : nthWaypoint, |
| 377 | |
| 378 | waypoint_name : options.waypointName |
| 379 | }; |
| 380 | return this.tokenize ( language, instruction, replaceTokens, options ); |
| 381 | } |
| 382 | |
| 383 | grammarize ( language, nameToProceed, grammar ) { |
| 384 | if ( grammar && grammars && grammars[ language ] && grammars[ language ][ OUR_VERSION ] ) { |
| 385 | const rules = grammars[ language ][ OUR_VERSION ][ grammar ]; |
| 386 | if ( rules ) { |
| 387 | let nameWithSpace = ' ' + nameToProceed + ' '; |
| 388 | const flags = grammars[ language ].meta.regExpFlags || ''; |
| 389 | rules.forEach ( function ( rule ) { |
| 390 | const re = new RegExp ( rule[ ZERO ], flags ); |
| 391 | nameWithSpace = nameWithSpace.replace ( re, rule[ ONE ] ); |
| 392 | } ); |
| 393 | |
| 394 | return nameWithSpace.trim (); |
| 395 | } |
| 396 | } |
| 397 | return nameToProceed; |
| 398 | } |
| 399 | |
| 400 | |
| 401 | tokenize ( language, instruction, tokens, options ) { |
| 402 | const that = this; |
| 403 | let startedWithToken = false; |
| 404 | const output = instruction.replace ( |
| 405 | |
| 406 | /\{(\w+)(?::(\w+))?\}/g, function ( token, tag, grammar, offset ) { |
| 407 | let value = tokens[ tag ]; |
| 408 | if ( 'undefined' === typeof value ) { |
| 409 | return token; |
| 410 | } |
| 411 | value = that.grammarize ( language, value, grammar ); |
| 412 | if ( ZERO === offset && instructions[ language ].meta.capitalizeFirstLetter ) { |
| 413 | startedWithToken = true; |
| 414 | value = that.capitalizeFirstLetter ( language, value ); |
| 415 | } |
| 416 | if ( options && options.formatToken ) { |
| 417 | value = options.formatToken ( tag, value ); |
| 418 | } |
| 419 | return value; |
| 420 | } |
| 421 | ) |
| 422 | .replace ( / {2}/g, ' ' ); |
| 423 | if ( ! startedWithToken && instructions[ language ].meta.capitalizeFirstLetter ) { |
| 424 | return this.capitalizeFirstLetter ( language, output ); |
| 425 | } |
| 426 | return output; |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | |
| 431 | |
| 432 | |
| 433 | |
| 434 | |
| 435 | |
| 436 | |
| 437 | |
| 438 | const theOsrmTextInstructions = new OsrmTextInstructions ( ); |
| 439 | |
| 440 | export default theOsrmTextInstructions; |
| 441 | |
| 442 | |
| 443 | |
| 444 | |
| 445 | |