File : routeProviders/MapboxRouteProvider.js

1
/*
2
Copyright - 2017 - wwwouaiebe - Contact: http//www.ouaie.be/
3
4
This  program is free software;
5
you can redistribute it and/or modify it under the terms of the
6
GNU General Public License as published by the Free Software Foundation;
7
either version 3 of the License, or any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program; if not, write to the Free Software
16
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
*/
18
/*
19
Changes:
20
    - v4.0.0:
21
        - created from v3.6.0
22
Doc reviewed 202208
23
 */
24
25
import PolylineEncoder from '../core/lib/PolylineEncoder.js';
26
import ItineraryPoint from '../data/ItineraryPoint.js';
27
import Maneuver from '../data/Maneuver.js';
28
import BaseRouteProvider from '../routeProviders/BaseRouteProvider.js';
29
import theOsrmTextInstructions from '../routeProviders/OsrmTextInstructions.js';
30
import { ICON_LIST } from '../routeProviders/IconList.js';
31
import { ZERO, ONE, TWO, LAT_LNG, HTTP_STATUS_OK } from '../main/Constants.js';
32
33
/* ------------------------------------------------------------------------------------------------------------------------- */
34
/**
35
This class implements the BaseRouteProvider for Mapbox. It's not possible to instanciate
36
this class because the class is not exported from the module. Only one instance is created and added to the list
37
of Providers of TravelNotes
38
*/
39
/* ------------------------------------------------------------------------------------------------------------------------- */
40
41
class MapboxRouteProvider extends BaseRouteProvider {
42
43
    /**
44
    The provider key. Will be set by TravelNotes
45
    @type {String}
46
    */
47
48
    #providerKey;
49
50
    /**
51
    A reference to the edited route
52
    @type {Route}
53
    */
54
55
    #route;
56
57
    /**
58
    The round value used by PolylineEncoder
59
    @type {Number}
60
    */
61
    // eslint-disable-next-line no-magic-numbers
62
    static get #ROUND_VALUE ( ) { return 6; }
63
64
    /**
65
    Parse the response from the provider and add the received itinerary to the route itinerary
66
    @param {JsonObject} response the itinerary received from the provider
67
    @param {function} onOk a function to call when the response is parsed correctly
68
    @param {function} onError a function to call when an error occurs
69
    */
70
71
    #parseResponse ( response, onOk, onError ) {
72
73
        if ( 'Ok' !== response.code ) {
74
            onError ( new Error ( 'Response code not ok' ) );
75
            return;
76
        }
77
78
        if ( ZERO === response.routes.length ) {
79
            onError ( new Error ( 'Route not found' ) );
80
            return;
81
        }
82
83
        const polylineEncoder = new PolylineEncoder ( );
84
        this.#route.itinerary.itineraryPoints.removeAll ( );
85
        this.#route.itinerary.maneuvers.removeAll ( );
86
        this.#route.itinerary.hasProfile = false;
87
        this.#route.itinerary.ascent = ZERO;
88
        this.#route.itinerary.descent = ZERO;
89
        response.routes [ ZERO ].geometry = polylineEncoder.decode (
90
            response.routes [ ZERO ].geometry, [ MapboxRouteProvider.#ROUND_VALUE, MapboxRouteProvider.#ROUND_VALUE ]
91
        );
92
        response.routes [ ZERO ].legs.forEach (
93
            leg => {
94
                let lastPointWithDistance = ZERO;
95
                leg.steps.forEach (
96
                    step => {
97
                        step.geometry = polylineEncoder.decode (
98
                            step.geometry,
99
                            [ MapboxRouteProvider.#ROUND_VALUE, MapboxRouteProvider.#ROUND_VALUE ]
100
                        );
101
                        if (
102
                            'arrive' === step.maneuver.type
103
                            &&
104
                            TWO === step.geometry.length
105
                            &&
106
                            step.geometry [ ZERO ] [ ZERO ] === step.geometry [ ONE ] [ ZERO ]
107
                            &&
108
                            step.geometry [ ZERO ] [ ONE ] === step.geometry [ ONE ] [ ONE ]
109
                        ) {
110
                            step.geometry.pop ( );
111
                        }
112
113
                        const maneuver = new Maneuver ( );
114
                        maneuver.iconName =
115
                            ICON_LIST [ step.maneuver.type ]
116
                                ?
117
                                ICON_LIST [ step.maneuver.type ] [ step.maneuver.modifier ]
118
                                ||
119
                                ICON_LIST [ step.maneuver.type ] [ 'default' ]
120
                                :
121
                                ICON_LIST [ 'default' ] [ 'default' ];
122
                        maneuver.instruction = theOsrmTextInstructions.compile ( this.userLanguage, step );
123
                        maneuver.duration = step.duration;
124
                        let distance = ZERO;
125
                        for (
126
                            let geometryCounter = ZERO;
127
                            ( ONE === step.geometry.length )
128
                                ?
129
                                ( ONE > geometryCounter )
130
                                :
131
                                ( geometryCounter < step.geometry.length );
132
                            geometryCounter ++ ) {
133
                            const itineraryPoint = new ItineraryPoint ( );
134
                            itineraryPoint.latLng = [
135
                                step.geometry [ geometryCounter ] [ ZERO ],
136
                                step.geometry [ geometryCounter ] [ ONE ]
137
                            ];
138
139
                            itineraryPoint.distance =
140
                                leg.annotation.distance [ lastPointWithDistance ]
141
                                    ?
142
                                    leg.annotation.distance [ lastPointWithDistance ]
143
                                    :
144
                                    ZERO;
145
                            this.#route.itinerary.itineraryPoints.add ( itineraryPoint );
146
                            if ( geometryCounter !== step.geometry.length - ONE ) {
147
                                distance += itineraryPoint.distance;
148
                                lastPointWithDistance ++;
149
                            }
150
                            if ( ZERO === geometryCounter ) {
151
                                maneuver.itineraryPointObjId = itineraryPoint.objId;
152
                            }
153
                        }
154
                        maneuver.distance = distance;
155
                        this.#route.itinerary.maneuvers.add ( maneuver );
156
                    }
157
                );
158
            }
159
        );
160
161
        const wayPointsIterator = this.#route.wayPoints.iterator;
162
        response.waypoints.forEach (
163
            wayPoint => {
164
                if ( ! wayPointsIterator.done ) {
165
                    wayPointsIterator.value.latLng = [ wayPoint.location [ ONE ], wayPoint.location [ ZERO ] ];
166
                }
167
            }
168
        );
169
170
        onOk ( this.#route );
171
    }
172
173
    /**
174
    Gives the url to call
175
    @return {String} a string with the url, wayPoints, transitMode, user language and Api key
176
    */
177
178
    #getUrl ( ) {
179
        let wayPointsString = null;
180
        this.#route.wayPoints.forEach (
181
            wayPoint => {
182
                wayPointsString = wayPointsString ? wayPointsString + ';' : '';
183
                wayPointsString +=
184
                    wayPoint.lng.toFixed ( LAT_LNG.fixed ) + ',' +
185
                    wayPoint.lat.toFixed ( LAT_LNG.fixed );
186
            }
187
        );
188
189
        let profile = '';
190
        switch ( this.#route.itinerary.transitMode ) {
191
        case 'car' :
192
            profile = 'mapbox/driving/';
193
            break;
194
        case 'bike' :
195
            profile = 'mapbox/cycling/';
196
            break;
197
        case 'pedestrian' :
198
            profile = 'mapbox/walking/';
199
            break;
200
        default :
201
            return;
202
        }
203
        return 'https://api.mapbox.com/directions/v5/' +
204
            profile +
205
            wayPointsString +
206
            '?geometries=polyline6&overview=full&steps=true&annotations=distance&access_token=' +
207
            this.#providerKey;
208
    }
209
210
    /**
211
    Implementation of the base class #getRoute ( )
212
    @param {function} onOk the Promise Success handler
213
    @param {function} onError the Promise Error handler
214
    */
215
216
    #getRoute ( onOk, onError ) {
217
        fetch ( this.#getUrl ( ) )
218
            .then (
219
                response => {
220
                    if ( HTTP_STATUS_OK === response.status && response.ok ) {
221
                        response.json ( )
222
                            .then ( result => this.#parseResponse ( result, onOk, onError ) );
223
                    }
224
                    else {
225
                        onError ( new Error ( 'Invalid status ' + response.status ) );
226
                    }
227
                }
228
            )
229
            .catch (
230
231
                // calling onError without parameters because fetch don't accecpt to add something as parameter :-(...
232
                ( ) => { onError ( ); }
233
            );
234
    }
235
236
    /**
237
    constructor
238
    */
239
240
    constructor ( ) {
241
        super ( );
242
        this.#providerKey = '';
243
    }
244
245
    /**
246
    Call the provider, using the waypoints defined in the route and, on success,
247
    complete the route with the data from the provider
248
    @param {Route} route The route to witch the data will be added
249
    @return {Promise} A Promise. On success, the Route is completed with the data given by the provider.
250
    */
251
252
    getPromiseRoute ( route ) {
253
        this.#route = route;
254
        return new Promise ( ( onOk, onError ) => this.#getRoute ( onOk, onError ) );
255
    }
256
257
    /**
258
    The icon used in the ProviderToolbarUI.
259
    Overload of the base class icon property
260
    @type {String}
261
    */
262
263
    get icon ( ) {
264
        return '' +
265
            'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWX' +
266
            'MAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AocEwcBrMn63AAAAk1JREFUSMftlj9oU1EUxn8neQ0thE6lgpO1TiptHhShJiIu2qHJA6' +
267
            'FudtBRUBcpouJUnRyqgpODVcG62LROHfzHexGiJWkEQbCr0IqVYKPStDkOpuYRfW1eTNCh33Tvufec717udz8ObOFfITqV3XA9Nj' +
268
            'n3W+xAMuO7pnhs7AQiwCqwpvBNVN47Vu8SQDSZwbFMqsexyUyHinQjtAEBwACyTiKyWM1heBzyMHDXdbplRCeiyexVCei8HTfpf5' +
269
            'gCwLFM9k/lEF3bpSIXgWNAm6vWceBercQrVfMwcBKhvVRcOwEst2zbXlldXQljGFeAoRpqbUjshSExgo9iM6kHLw7uUIDYTEr0ez' +
270
            'DuQeoJw7/8ZLRUCD/ZNz6/AFAqFDolWBr1WyVQh/C7JKgj6eFu0sPdSFBHgC6/RWq7sbCI0g60/gzoqWhy7v762LXzC/AR2NmQG6' +
271
            'tyE3jnCoUQHUN0DAi54m+BGw27sUAGyAOjZYUD9Fdty4vqLRX51Mg3bnUSkevAm6rc9XwFXtuWeafyHI0hDgCI6AXg8x/WlwTO+6' +
272
            'npS9V23HwKJMtW+ss+FCbsRORVU79TMdByFlhwhT60hELnmvqP+6dzpAf35BG9DBSBoqheej6w+2vsca55xC/jPei04sTN20AKsG' +
273
            '3LHN87cg17sKe5ztXHbFnHclrgDEDHwFGa41wuzMb7iCbncKzeHEBsKsuzQ74dsy6vxrF6K0pPROrqdOoibgT+O+LQJvONUFOul7' +
274
            'hmgCNlhzKArA/i+nK92tvN2t6/zd1C0/ADiOy3l0UZHxAAAAAASUVORK5CYII=';
275
    }
276
277
    /**
278
    The provider name.
279
    Overload of the base class name property
280
    @type {String}
281
    */
282
283
    get name ( ) { return 'Mapbox'; }
284
285
    /**
286
    The title to display in the ProviderToolbarUI button.
287
    Overload of the base class title property
288
    @type {String}
289
    */
290
291
    get title ( ) { return 'Mapbox'; }
292
293
    /**
294
    The possible transit modes for the provider.
295
    Overload of the base class transitModes property
296
    Must be a subarray of [ 'bike', 'pedestrian', 'car', 'train', 'line', 'circle' ]
297
    @type {Array.<String>}
298
    */
299
300
    get transitModes ( ) { return [ 'bike', 'pedestrian', 'car' ]; }
301
302
    /**
303
    A boolean indicating when a provider key is needed for the provider.
304
    Overload of the base class providerKeyNeeded property
305
    @type {Boolean}
306
    */
307
308
    get providerKeyNeeded ( ) { return true; }
309
310
    /**
311
    The provider key.
312
    Overload of the base class providerKey property
313
    */
314
315
    set providerKey ( providerKey ) { this.#providerKey = providerKey; }
316
317
    /**
318
    The user language. Overload of the base class userLanguage property.
319
    It's needed to overload the setter AND the getter otherwise the getter returns undefined
320
    @type {String}
321
    */
322
323
    get userLanguage ( ) { return super.userLanguage; }
324
    set userLanguage ( userLanguage ) {
325
        super.userLanguage = theOsrmTextInstructions.loadLanguage ( userLanguage );
326
    }
327
}
328
329
window.TaN.addProvider ( MapboxRouteProvider );
330
331
/* --- End of file --------------------------------------------------------------------------------------------------------- */
332