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 | |