File : routeProviders/GraphHopperRouteProvider.js

1
/*
2
Copyright - 2017 2023 - wwwouaiebe - Contact: https://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
30
import { ZERO, TWO, LAT, LNG, ELEVATION, LAT_LNG, HTTP_STATUS_OK, DISTANCE } from '../main/Constants.js';
31
32
/* ------------------------------------------------------------------------------------------------------------------------- */
33
/**
34
This class implements the BaseRouteProvider interface for Graphhopper.
35
*/
36
/* ------------------------------------------------------------------------------------------------------------------------- */
37
38
class GraphHopperRouteProvider extends BaseRouteProvider {
39
40
    /**
41
    The provider key. Will be set by TravelNotes
42
    @type {String}
43
    */
44
45
    #providerKey;
46
47
    /**
48
    A reference to the edited route
49
    @type {Route}
50
    */
51
52
    #route;
53
54
    /**
55
    The round value used by PolylineEncoder
56
    @type {Number}
57
    */
58
    // eslint-disable-next-line no-magic-numbers
59
    static get #ROUND_VALUE ( ) { return 5; }
60
61
    /**
62
    Enum for icons
63
    @type {Array.<String>}
64
    */
65
66
    static get #ICON_LIST ( ) {
67
        return [
68
            'kUndefined',
69
            'kTurnSharpLeft', // TURN_SHARP_LEFT = -3
70
            'kTurnLeft', // TURN_LEFT = -2
71
            'kTurnSlightLeft', // TURN_SLIGHT_LEFT = -1
72
            'kContinueStraight', // CONTINUE_ON_STREET = 0
73
            'kTurnSlightRight', // TURN_SLIGHT_RIGHT = 1
74
            'kTurnRight', // TURN_RIGHT = 2
75
            'kTurnSharpRight', // TURN_SHARP_RIGHT = 3
76
            'kArriveDefault', // FINISH = 4
77
            'kArriveDefault', // VIA_REACHED = 5
78
            'kRoundaboutRight' // USE_ROUNDABOUT = 6
79
        ];
80
    }
81
82
    /**
83
    Parse the response from the provider and add the received itinerary to the route itinerary
84
    @param {JsonObject} response the itinerary received from the provider
85
    @param {function} onOk a function to call when the response is parsed correctly
86
    @param {function} onError a function to call when an error occurs
87
    */
88
89
    #parseResponse ( response, onOk, onError ) {
90
91
        if ( ZERO === response.paths.length ) {
92
            onError ( 'Route not found' );
93
            return;
94
        }
95
96
        const polylineEncoder = new PolylineEncoder ( );
97
        this.#route.itinerary.itineraryPoints.removeAll ( );
98
        this.#route.itinerary.maneuvers.removeAll ( );
99
        this.#route.itinerary.hasProfile = true;
100
        this.#route.itinerary.ascent = ZERO;
101
        this.#route.itinerary.descent = ZERO;
102
        response.paths.forEach (
103
            path => {
104
                path.points = polylineEncoder.decode (
105
                    path.points,
106
                    [ GraphHopperRouteProvider.#ROUND_VALUE, GraphHopperRouteProvider.#ROUND_VALUE, TWO ]
107
                );
108
                // eslint-disable-next-line camelcase
109
                path.snapped_waypoints = polylineEncoder.decode (
110
                    path.snapped_waypoints,
111
                    [ GraphHopperRouteProvider.#ROUND_VALUE, GraphHopperRouteProvider.#ROUND_VALUE, TWO ]
112
                );
113
                const itineraryPoints = [];
114
                for ( let pointsCounter = ZERO; pointsCounter < path.points.length; pointsCounter ++ ) {
115
                    const itineraryPoint = new ItineraryPoint ( );
116
                    itineraryPoint.lat = path.points [ pointsCounter ] [ LAT ];
117
                    itineraryPoint.lng = path.points [ pointsCounter ] [ LNG ];
118
                    itineraryPoint.elev = path.points [ pointsCounter ] [ ELEVATION ];
119
                    itineraryPoints.push ( itineraryPoint );
120
                    this.#route.itinerary.itineraryPoints.add ( itineraryPoint );
121
                }
122
123
                let previousIconName = '';
124
                path.instructions.forEach (
125
                    instruction => {
126
                        const maneuver = new Maneuver ( );
127
                        // eslint-disable-next-line no-magic-numbers
128
                        maneuver.iconName = GraphHopperRouteProvider.#ICON_LIST [ instruction.sign + 4 || ZERO ];
129
                        if ( 'kArriveDefault' === previousIconName && 'kContinueStraight' === maneuver.iconName ) {
130
                            maneuver.iconName = 'kDepartDefault';
131
                        }
132
                        previousIconName = maneuver.iconName;
133
                        maneuver.instruction = instruction.text || '';
134
                        maneuver.duration = instruction.time / DISTANCE.metersInKm;
135
                        maneuver.distance = instruction.distance;
136
                        maneuver.itineraryPointObjId = itineraryPoints [ instruction.interval [ ZERO ] ].objId;
137
                        this.#route.itinerary.maneuvers.add ( maneuver );
138
139
                    }
140
                );
141
142
                const wayPointsIterator = this.#route.wayPoints.iterator;
143
                path.snapped_waypoints.forEach (
144
                    latLngElev => {
145
                        if ( ! wayPointsIterator.done ) {
146
                            wayPointsIterator.value.lat = latLngElev [ LAT ];
147
                            wayPointsIterator.value.lng = latLngElev [ LNG ];
148
                        }
149
                    }
150
                );
151
            }
152
        );
153
154
        onOk ( this.#route );
155
    }
156
157
    /**
158
    Gives the url to call
159
    @return {String} a string with the url, wayPoints, transitMode, user language and Api key
160
    */
161
162
    #getUrl ( ) {
163
        let wayPointsString = null;
164
        this.#route.wayPoints.forEach (
165
            wayPoint => {
166
                wayPointsString = wayPointsString ? wayPointsString + '&' : '';
167
                wayPointsString +=
168
                    'point=' +
169
                    wayPoint.lat.toFixed ( LAT_LNG.fixed ) + ',' +
170
                    wayPoint.lng.toFixed ( LAT_LNG.fixed );
171
            }
172
        );
173
174
        let vehicle = '';
175
        switch ( this.#route.itinerary.transitMode ) {
176
        case 'bike' :
177
            vehicle = 'bike';
178
            break;
179
        case 'pedestrian' :
180
            vehicle = 'foot';
181
            break;
182
        case 'car' :
183
            vehicle = 'car';
184
            break;
185
        default :
186
            break;
187
        }
188
189
        return 'https://graphhopper.com/api/1/route?' + wayPointsString +
190
            '&instructions=true&elevation=true&type=json&key=' + this.#providerKey + '&locale=' + this.userLanguage +
191
            '&vehicle=' + vehicle;
192
    }
193
194
    /**
195
    Overload of the base class #getRoute ( ) method
196
    @param {function} onOk the Promise Success handler
197
    @param {function} onError the Promise Error handler
198
    */
199
200
    #getRoute ( onOk, onError ) {
201
        fetch ( this.#getUrl ( ) )
202
            .then (
203
                response => {
204
                    if ( HTTP_STATUS_OK === response.status && response.ok ) {
205
                        response.json ( )
206
                            .then ( result => this.#parseResponse ( result, onOk, onError ) );
207
                    }
208
                    else {
209
                        onError ( new Error ( 'Invalid status ' + response.status ) );
210
                    }
211
                }
212
            )
213
            .catch (
214
215
                // calling onError without parameters because fetch don't accecpt to add something as parameter :-(...
216
                ( ) => { onError ( ); }
217
            );
218
    }
219
220
    /**
221
    The constructor
222
    */
223
224
    constructor ( ) {
225
        super ( );
226
        this.#providerKey = '';
227
    }
228
229
    /**
230
    Call the provider, using the waypoints defined in the route and, on success,
231
    complete the route with the data from the provider
232
    @param {Route} route The route to witch the data will be added
233
    @return {Promise} A Promise. On success, the Route is completed with the data given by the provider.
234
    */
235
236
    getPromiseRoute ( route ) {
237
        this.#route = route;
238
        return new Promise ( ( onOk, onError ) => this.#getRoute ( onOk, onError ) );
239
    }
240
241
    /**
242
    The icon used in the ProviderToolbarUI.
243
    Overload of the base class icon property
244
    @type {String}
245
    */
246
247
    get icon ( ) {
248
        return '' +
249
            'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAIAAAC0Ujn1AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3' +
250
            'RJTUUH4AocDyYTxtPEYwAABGtJREFUSMedVU1MXFUUPue+9+bNAMMUZrCFVAsiDEGpJlpdtCY0sTU1xIA1QNSNIcYGuyAuxBp1oU' +
251
            '1DYu2iNlKsJopVYy2lkpiyoCCptLWF1DKFkpb/YShvmBmYX97/dUECw5sBpj2r97577pdzv3vud/BLzw8XF3s5ZGG9QAQA0CggAE' +
252
            'GgFDYLRVffsu/HUlcNAyQ5J0NApzGXXxwLKvNLhCNcbrqlNJvfYaWavjE7hyzLIUuTFYIskd2RByduRfu9im9pBed3ZG79oCzr9Q' +
253
            'KqrMtOgXLIrquDOBqcONQjeyIGXJoKTX/URyUtu6aIStoGhZOk4mpBeeL97kTelXB/dj3QPoYseThq5HD6yFV5Npq4xMadcvZof8' +
254
            'zlRwZTpUYTE7w0Fb4ya8CLi4vb2toGBgZ6enpqa2sBQAvLvtYRLaauR52gNaW+s/cMWGFhYUdHh9PpXP4tLy/Pz89vamqSugXze7' +
255
            'r8JJMCNcGl4YDkDq/BCGloaHA6nVPy3GnvhZvRYQdrq/n0QFrzqVgw8sQV03gh6lTfRBBEEO8F1YAYD9rt9sP1hyflB4cmm9oDPY' +
256
            'Lsd8XGjkyfsrxgB4DB367tztipUHUTaqqDNBMxNGxlZSUQOOO96JG9PDERJCwyZjSpqAPAqGd898xTKtE2o1Z1bUE0ZFRUVLiptz' +
257
            '96FyC+GVAcCy5/3fnr5nO2Ei1BE2K4w8Q3VlJSMhpzT8tzGHclakCUJkLLf5c6O19z7EnUhBhMg8ngDBnhaOSuOBlvBsTC+n9d7a' +
258
            'KhoaFnYAcibkjNoml7BqzNOd92PsyKqzsJKt6Y7+eRlQRZlsNTC9t4OwW6vtYULE9ncw5LPPj18eOuvluEEkAABH1Jnfn8XzUsx+' +
259
            'f4Zrx55hy6UV/rNG2nw7onN9A+vmq+kvLLmydyDjotpVm6rC92TkWuzxnOHvWHHivNAqDxV218jVSneY3PZ4+yo664N7lE58+ObO' +
260
            'T9MdnGWuka5kR70qmeTvb9/u6B8lch5WCBSSPmzTwEgAWmV/3vWPsnb5w72NxyWhAERVEopQzD8DwviqIgCIYtNmtmAOQU/BqARe' +
261
            'bY7I+Z7xQODAy4+m/3Xv67u6v7Wt9V1+3B6urqxPxtubmJNoLP3nk76QBbHkIFfN5e264XM0rzTA4GiFvwvOJ8ORQJGZIDiwsnY2' +
262
            '1/+LoYJMt704h53QGGgCrV7ovu+6L7O+HC8rQUml2JvEVFRVm2LYvBEKYiSLKZgap/Sfh2MHGlvr4+CpJXCcDaB5kqNTEznqP9VD' +
263
            'VKl5OTU1dXJ8i+McnziFVTSdtaXwZ5vAFvaWmxWq03wsMLagjhkaqmGiX56Sdv/PTxh422TBvP8w6Ho7W1taqqSqHKGV+7CblUOy' +
264
            'RpZLOZXxU3lGkF8x5he/7jAKBQ9YvZ78/5u8yEj++uNGLGUlcNC0yK1DpQC5r2btm13/ZSHueYleb/XOi9HOpnkVnbuGBCBhvd33' +
265
            'Qs/MMhAw8TPHIssirVJConsRSq1tr3/Q+O4QqEHeMWIQAAAABJRU5ErkJggg==';
266
    }
267
268
    /**
269
    The provider name.
270
    Overload of the base class name property
271
    @type {String}
272
    */
273
274
    get name ( ) { return 'GraphHopper'; }
275
276
    /**
277
    The title to display in the ProviderToolbarUI button.
278
    Overload of the base class title property
279
    @type {String}
280
    */
281
282
    get title ( ) { return 'GraphHopper'; }
283
284
    /**
285
    The possible transit modes for the provider.
286
    Overload of the base class transitModes property
287
    Must be a subarray of [ 'bike', 'pedestrian', 'car', 'train', 'line', 'circle' ]
288
    @type {Array.<String>}
289
    */
290
291
    get transitModes ( ) { return [ 'bike', 'pedestrian', 'car' ]; }
292
293
    /**
294
    A boolean indicating when a provider key is needed for the provider.
295
    Overload of the base class providerKeyNeeded property
296
    @type {Boolean}
297
    */
298
299
    get providerKeyNeeded ( ) { return true; }
300
301
    /**
302
    The provider key.
303
    Overload of the base class providerKey property
304
    */
305
306
    set providerKey ( providerKey ) { this.#providerKey = providerKey; }
307
}
308
309
window.TaN.addProvider ( GraphHopperRouteProvider );
310
311
/* --- End of file --------------------------------------------------------------------------------------------------------- */
312