From 329d51a68f0527ff9652fffdb27d7e04aa8295ae Mon Sep 17 00:00:00 2001 From: Patrick Kaminski Date: Tue, 23 Oct 2018 21:17:03 -0300 Subject: [PATCH 1/7] Add `off` method to unsubscribe events Fixes #193 Inspired by tristen commit ref #40 --- API.md | 22 +++++++++++++++++++++- src/actions/index.js | 28 ++++++++++++++++++++++++++++ src/directions.js | 11 +++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index 36fd8539..6b18db6e 100644 --- a/API.md +++ b/API.md @@ -16,6 +16,7 @@ - [getWaypoints](#getwaypoints) - [removeRoutes](#removeroutes) - [on](#on) + - [off](#off) ## MapboxDirections @@ -168,7 +169,8 @@ Subscribe to events that happen within the plugin. **Parameters** -- `type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** name of event. Available events and the data passed into their respective event objects are:- **clear** `{ type: } Type is one of 'origin' or 'destination'` +- `type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** name of event. Available events and the data passed into their respective event objects are: + - **clear** `{ type: } Type is one of 'origin' or 'destination'` - **loading** `{ type: } Type is one of 'origin' or 'destination'` - **profile** `{ profile } Profile is one of 'driving', 'walking', or 'cycling'` - **origin** `{ feature } Fired when origin is set` @@ -178,3 +180,21 @@ Subscribe to events that happen within the plugin. - `fn` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** function that's called when the event is emitted. Returns **[MapboxDirections](#mapboxdirections)** this; + +### off + +Unsubscribe to events previously registered. + +**Parameters** + +- `type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** name of event. Available events and the data passed into their respective event objects are: + - **clear** `{ type: } Type is one of 'origin' or 'destination'` + - **loading** `{ type: } Type is one of 'origin' or 'destination'` + - **profile** `{ profile } Profile is one of 'driving', 'walking', or 'cycling'` + - **origin** `{ feature } Fired when origin is set` + - **destination** `{ feature } Fired when destination is set` + - **route** `{ route } Fired when a route is updated` + - **error** \`{ error } Error as string +- `fn` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** function used when the event was registered. + +Returns **[MapboxDirections](#mapboxdirections)** this; diff --git a/src/actions/index.js b/src/actions/index.js index 4d118568..d1ed88a9 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -315,6 +315,34 @@ export function eventSubscribe(type, fn) { }; } +export function eventUnsubscribe(type, fn) { + return (dispatch, getState) => { + const { events } = getState(); + + if (!events[type]) { + return { + type: types.EVENTS, + events + }; + } + + if (fn) { + // Drop the event if it exists in the events object + const fnToDelete = events[type].indexOf(fn); + if (fnToDelete >= 0) { + events[type].splice(fnToDelete, 1); + } + } else { + delete events[type]; + } + + return { + type: types.EVENTS, + events: events + }; + }; +} + export function eventEmit(type, data) { return (dispatch, getState) => { const { events } = getState(); diff --git a/src/directions.js b/src/directions.js index 819cd991..39bc756f 100644 --- a/src/directions.js +++ b/src/directions.js @@ -551,4 +551,15 @@ export default class MapboxDirections { this.actions.eventSubscribe(type, fn); return this; } + + /** + * Unsubscribe to events + * @param {String} type name of event. Available events are outlined in `on` + * @param {Function} fn optional. The function that's called when the event is emitted. + * @returns {Directions} this; + */ + off(type, fn) { + this.actions.eventUnsubscribe(type, fn); + return this; + } } From 9d372ad6b834c7c76ab53a65094463ef19c55194 Mon Sep 17 00:00:00 2001 From: Patrick Kaminski Date: Tue, 23 Oct 2018 21:27:27 -0300 Subject: [PATCH 2/7] Instructions for all legs Fixes 181 --- src/controls/instructions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controls/instructions.js b/src/controls/instructions.js index 9f284348..7bec5f2a 100644 --- a/src/controls/instructions.js +++ b/src/controls/instructions.js @@ -38,6 +38,7 @@ export default class Instructions { if (directions.length && shouldRender) { const direction = this.directions = directions[routeIndex]; + const allSteps = direction.legs.reduce((legs, leg) => legs.concat(leg.steps), []); if (compile) { direction.legs.forEach(function(leg) { @@ -50,7 +51,7 @@ export default class Instructions { this.container.innerHTML = instructionsTemplate({ routeIndex, routes: directions.length, - steps: direction.legs[0].steps, // Todo: Respect all legs, + steps: allSteps, format: utils.format[unit], duration: utils.format[unit](direction.distance), distance: utils.format.duration(direction.duration) From 273d7ff2e4270ad5f0c5490412db6cbfeb4d88c9 Mon Sep 17 00:00:00 2001 From: Patrick Kaminski Date: Tue, 23 Oct 2018 22:02:01 -0300 Subject: [PATCH 3/7] fitBounds to all points (origin, destination and waypoints) ref 116 --- src/controls/inputs.js | 6 ++++-- src/directions.js | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/controls/inputs.js b/src/controls/inputs.js index 7b235dc8..4b39b9c6 100644 --- a/src/controls/inputs.js +++ b/src/controls/inputs.js @@ -36,16 +36,18 @@ export default class Inputs { } animateToCoordinates(mode, coords) { - const { origin, destination } = this.store.getState(); + const { origin, destination, waypoints } = this.store.getState(); if (origin.geometry && destination.geometry && !isEqual(origin.geometry, destination.geometry)) { + const ways = waypoints.filter((waypoint) => (waypoint.geometry)) + // Animate map to fit bounds. const bb = extent({ type: 'FeatureCollection', - features: [origin, destination] + features: [origin, destination, ...ways] }); this._map.fitBounds([[bb[0], bb[1]], [bb[2], bb[3]]], { padding: 80 }); diff --git a/src/directions.js b/src/directions.js index 819cd991..c2d0a883 100644 --- a/src/directions.js +++ b/src/directions.js @@ -293,7 +293,6 @@ export default class MapboxDirections { } } else { this.actions.setDestinationFromCoordinates(coords); - this._map.flyTo({ center: coords }); } } } From 7390b3a32ec1dbfa254958f34e54b5114b9ec854 Mon Sep 17 00:00:00 2001 From: Patrick Kaminski Date: Tue, 23 Oct 2018 21:17:03 -0300 Subject: [PATCH 4/7] Add `off` method to unsubscribe events Fixes #193 Inspired by tristen commit ref #40 --- API.md | 22 +++++++++++++++++++++- src/actions/index.js | 28 ++++++++++++++++++++++++++++ src/directions.js | 11 +++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index 36fd8539..6b18db6e 100644 --- a/API.md +++ b/API.md @@ -16,6 +16,7 @@ - [getWaypoints](#getwaypoints) - [removeRoutes](#removeroutes) - [on](#on) + - [off](#off) ## MapboxDirections @@ -168,7 +169,8 @@ Subscribe to events that happen within the plugin. **Parameters** -- `type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** name of event. Available events and the data passed into their respective event objects are:- **clear** `{ type: } Type is one of 'origin' or 'destination'` +- `type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** name of event. Available events and the data passed into their respective event objects are: + - **clear** `{ type: } Type is one of 'origin' or 'destination'` - **loading** `{ type: } Type is one of 'origin' or 'destination'` - **profile** `{ profile } Profile is one of 'driving', 'walking', or 'cycling'` - **origin** `{ feature } Fired when origin is set` @@ -178,3 +180,21 @@ Subscribe to events that happen within the plugin. - `fn` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** function that's called when the event is emitted. Returns **[MapboxDirections](#mapboxdirections)** this; + +### off + +Unsubscribe to events previously registered. + +**Parameters** + +- `type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** name of event. Available events and the data passed into their respective event objects are: + - **clear** `{ type: } Type is one of 'origin' or 'destination'` + - **loading** `{ type: } Type is one of 'origin' or 'destination'` + - **profile** `{ profile } Profile is one of 'driving', 'walking', or 'cycling'` + - **origin** `{ feature } Fired when origin is set` + - **destination** `{ feature } Fired when destination is set` + - **route** `{ route } Fired when a route is updated` + - **error** \`{ error } Error as string +- `fn` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** function used when the event was registered. + +Returns **[MapboxDirections](#mapboxdirections)** this; diff --git a/src/actions/index.js b/src/actions/index.js index 4d118568..d1ed88a9 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -315,6 +315,34 @@ export function eventSubscribe(type, fn) { }; } +export function eventUnsubscribe(type, fn) { + return (dispatch, getState) => { + const { events } = getState(); + + if (!events[type]) { + return { + type: types.EVENTS, + events + }; + } + + if (fn) { + // Drop the event if it exists in the events object + const fnToDelete = events[type].indexOf(fn); + if (fnToDelete >= 0) { + events[type].splice(fnToDelete, 1); + } + } else { + delete events[type]; + } + + return { + type: types.EVENTS, + events: events + }; + }; +} + export function eventEmit(type, data) { return (dispatch, getState) => { const { events } = getState(); diff --git a/src/directions.js b/src/directions.js index 819cd991..39bc756f 100644 --- a/src/directions.js +++ b/src/directions.js @@ -551,4 +551,15 @@ export default class MapboxDirections { this.actions.eventSubscribe(type, fn); return this; } + + /** + * Unsubscribe to events + * @param {String} type name of event. Available events are outlined in `on` + * @param {Function} fn optional. The function that's called when the event is emitted. + * @returns {Directions} this; + */ + off(type, fn) { + this.actions.eventUnsubscribe(type, fn); + return this; + } } From 8f99969144bca0fc45ff06d0226a2fdd181b9c79 Mon Sep 17 00:00:00 2001 From: Patrick Kaminski Date: Tue, 23 Oct 2018 21:27:27 -0300 Subject: [PATCH 5/7] Instructions for all legs Fixes 181 --- src/controls/instructions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controls/instructions.js b/src/controls/instructions.js index 9f284348..7bec5f2a 100644 --- a/src/controls/instructions.js +++ b/src/controls/instructions.js @@ -38,6 +38,7 @@ export default class Instructions { if (directions.length && shouldRender) { const direction = this.directions = directions[routeIndex]; + const allSteps = direction.legs.reduce((legs, leg) => legs.concat(leg.steps), []); if (compile) { direction.legs.forEach(function(leg) { @@ -50,7 +51,7 @@ export default class Instructions { this.container.innerHTML = instructionsTemplate({ routeIndex, routes: directions.length, - steps: direction.legs[0].steps, // Todo: Respect all legs, + steps: allSteps, format: utils.format[unit], duration: utils.format[unit](direction.distance), distance: utils.format.duration(direction.duration) From bedc369ba5c2adb786d89674f9df240ab4bb1eb7 Mon Sep 17 00:00:00 2001 From: Patrick Kaminski Date: Tue, 23 Oct 2018 22:02:01 -0300 Subject: [PATCH 6/7] fitBounds to all points (origin, destination and waypoints) ref 116 --- src/controls/inputs.js | 6 ++++-- src/directions.js | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/controls/inputs.js b/src/controls/inputs.js index 7b235dc8..4b39b9c6 100644 --- a/src/controls/inputs.js +++ b/src/controls/inputs.js @@ -36,16 +36,18 @@ export default class Inputs { } animateToCoordinates(mode, coords) { - const { origin, destination } = this.store.getState(); + const { origin, destination, waypoints } = this.store.getState(); if (origin.geometry && destination.geometry && !isEqual(origin.geometry, destination.geometry)) { + const ways = waypoints.filter((waypoint) => (waypoint.geometry)) + // Animate map to fit bounds. const bb = extent({ type: 'FeatureCollection', - features: [origin, destination] + features: [origin, destination, ...ways] }); this._map.fitBounds([[bb[0], bb[1]], [bb[2], bb[3]]], { padding: 80 }); diff --git a/src/directions.js b/src/directions.js index 39bc756f..b06217c4 100644 --- a/src/directions.js +++ b/src/directions.js @@ -293,7 +293,6 @@ export default class MapboxDirections { } } else { this.actions.setDestinationFromCoordinates(coords); - this._map.flyTo({ center: coords }); } } } From 38de4cfd3611cc46450ad8fbb535aab7afcceacb Mon Sep 17 00:00:00 2001 From: Patrick Kaminski Date: Wed, 28 Nov 2018 20:57:15 -0200 Subject: [PATCH 7/7] Fixes waypoint index Fix mapbox/mapbox-gl-directions#184 --- src/directions.js | 27 +++++++++++++++++++++++---- src/utils.js | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/directions.js b/src/directions.js index b06217c4..f3b0b76b 100644 --- a/src/directions.js +++ b/src/directions.js @@ -98,7 +98,9 @@ export default class MapboxDirections { * @returns {Control} `this` */ onRemove(map) { - this.container.parentNode.removeChild(this.container); + if (this.container.parentNode !== null) { + this.container.parentNode.removeChild(this.container); + } this.removeRoutes(); map.off('mousedown', this.onDragDown); map.off('mousemove', this.move); @@ -363,7 +365,7 @@ export default class MapboxDirections { _onDragUp() { if (!this.isDragging) return; - const { hoverMarker, origin, destination } = store.getState(); + const { hoverMarker, origin, destination, waypoints } = store.getState(); switch (this.isDragging.layer.id) { case 'directions-origin-point': @@ -375,7 +377,9 @@ export default class MapboxDirections { case 'directions-hover-point': // Add waypoint if a sufficent amount of dragging has occurred. if (hoverMarker.geometry && !utils.coordinateMatch(this.isDragging, hoverMarker)) { - this.actions.addWaypoint(0, hoverMarker); + const click = this.isDragging.geometry.coordinates; + const index = utils.getNextWaypoint(this.getRoute.bind(this), waypoints, click); + this.actions.addWaypoint(index, hoverMarker); } break; } @@ -520,7 +524,22 @@ export default class MapboxDirections { getWaypoints() { return store.getState().waypoints; } - + + /** + * Fetch all current points in a route. + * @returns {Array} route points + */ + getRoute() { + return this + ._map + .getSource('directions') + ._data + .features + .find(({geometry}) => geometry.type === 'LineString') + .geometry + .coordinates; + } + /** * Removes all routes and waypoints from the map. * diff --git a/src/utils.js b/src/utils.js index d0c2937a..2c0aee3b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -62,4 +62,43 @@ const format = { } }; -export default { format, coordinateMatch, createPoint, validCoords, wrap, roundWithOriginalPrecision }; +const distance = ([x1, y1], [x2, y2]) => + Math.hypot((x1 - x2), (y1 - y2)); + +const nearest = origin => + (p1, p2, index) => + (distance(origin, p1) < distance(origin, p2)) + ? p1 + : p2.concat(index); + + +const find = (route, origin) => route + .reduce(nearest(origin), [Infinity, Infinity, -1])[2]; + +const compare = point => + waypoint => + coordinateMatch(point, waypoint); + +const getNextWaypoint = (getRoute, waypoints, origin) => { + if (waypoints.length === 0) return 0; + + const route = getRoute(); + + for(let i = find(route, origin); i < route.length; i++) { + const index = waypoints.findIndex(compare({ geometry: { coordinates: route[i] } })); + if (index !== -1) { + return index; + } + } + return waypoints.length; +}; + +export default { + format, + coordinateMatch, + createPoint, + validCoords, + wrap, + roundWithOriginalPrecision, + getNextWaypoint +};