mapbox / mapbox-gl-directions

Directions plugin for mapbox-gl-js using Mapbox Directions API.
https://mapbox.com/mapbox-gl-js/example/mapbox-gl-directions/
ISC License
239 stars 129 forks source link

Changing map style causes plugin to throw errors #190

Open ryanhamley opened 6 years ago

ryanhamley commented 6 years ago

This bug was initially filed as https://github.com/mapbox/mapbox-gl-js/issues/7019 but I'm moving it here because the bug is in the plugin.

When you change a map's style, the route feature is removed causing this line to throw the error Error: The layer 'directions-route-line-alt' does not exist in the map's style and cannot be queried for features.

https://github.com/mapbox/mapbox-gl-directions/blob/31c7911a616a0e2e306742b30d87e33cdfd56c8f/src/directions.js#L304-L312

Steps to Trigger Behavior

  1. Add directions route to map
  2. Change base map style
  3. Move mouse around the map

Link to Demonstration

https://plnkr.co/edit/M8mj1WTH1KjDa1dhiUKw?p=preview

Expected Behavior

The plugin should be able to handle this situation so that no errors are thrown.

Actual Behavior

Errors are thrown.

cc @mikeomeara1

weifageo commented 5 years ago

I was experiencing the same issue as described in the OP and came up with this hack work around which involves removing all the Directions Layers and Source, changing the map style, then re-adding the Source and Layers once again. It is a tedious process and the user will have to query the directions control once again to show a route after the style has changed. Since no one has responded to this thread I thought this might be helpful for others suffering from this same issue.

function removeDirectionsLayers(){ map.removeLayer('directions-route-line-alt'); map.removeLayer('directions-route-line-casing'); map.removeLayer('directions-route-line'); map.removeLayer('directions-hover-point-casing'); map.removeLayer('directions-hover-point'); map.removeLayer('directions-waypoint-point-casing'); map.removeLayer('directions-waypoint-point'); map.removeLayer('directions-origin-point'); map.removeLayer('directions-origin-label'); map.removeLayer('directions-destination-point'); map.removeLayer('directions-destination-label'); map.removeSource('directions'); }

function addDirectionsLayers(){ // directions hack map.addSource('directions', { "type": "geojson", "data": { "type": "FeatureCollection", "features": [] } }); // these layers were found in the following source code: https://github.com/mapbox/mapbox-gl-directions/blob/master/src/directions_style.js map.addLayer({ 'id': 'directions-route-line-alt', 'type': 'line', 'source': 'directions', 'layout': { 'line-cap': 'round', 'line-join': 'round' }, 'paint': { 'line-color': '#bbb', 'line-width': 4 }, 'filter': [ 'all', ['in', '$type', 'LineString'], ['in', 'route', 'alternate'] ] }); map.addLayer({ 'id': 'directions-route-line-casing', 'type': 'line', 'source': 'directions', 'layout': { 'line-cap': 'round', 'line-join': 'round' }, 'paint': { 'line-color': '#2d5f99', 'line-width': 12 }, 'filter': [ 'all', ['in', '$type', 'LineString'], ['in', 'route', 'selected'] ] }); map.addLayer({ 'id': 'directions-route-line', 'type': 'line', 'source': 'directions', 'layout': { 'line-cap': 'butt', 'line-join': 'round' }, 'paint': { 'line-color': { 'property': 'congestion', 'type': 'categorical', 'default': '#4882c5', 'stops': [ ['unknown', '#4882c5'], ['low', '#4882c5'], ['moderate', '#f09a46'], ['heavy', '#e34341'], ['severe', '#8b2342'] ] }, 'line-width': 7 }, 'filter': [ 'all', ['in', '$type', 'LineString'], ['in', 'route', 'selected'] ] }); map.addLayer({ 'id': 'directions-hover-point-casing', 'type': 'circle', 'source': 'directions', 'paint': { 'circle-radius': 8, 'circle-color': '#fff' }, 'filter': [ 'all', ['in', '$type', 'Point'], ['in', 'id', 'hover'] ] }); map.addLayer({ 'id': 'directions-hover-point', 'type': 'circle', 'source': 'directions', 'paint': { 'circle-radius': 6, 'circle-color': '#3bb2d0' }, 'filter': [ 'all', ['in', '$type', 'Point'], ['in', 'id', 'hover'] ] }); map.addLayer({ 'id': 'directions-waypoint-point-casing', 'type': 'circle', 'source': 'directions', 'paint': { 'circle-radius': 8, 'circle-color': '#fff' }, 'filter': [ 'all', ['in', '$type', 'Point'], ['in', 'id', 'waypoint'] ] }); map.addLayer({ 'id': 'directions-waypoint-point', 'type': 'circle', 'source': 'directions', 'paint': { 'circle-radius': 6, 'circle-color': '#8a8bc9' }, 'filter': [ 'all', ['in', '$type', 'Point'], ['in', 'id', 'waypoint'] ] }); map.addLayer({ 'id': 'directions-origin-point', 'type': 'circle', 'source': 'directions', 'paint': { 'circle-radius': 18, 'circle-color': '#3bb2d0' }, 'filter': [ 'all', ['in', '$type', 'Point'], ['in', 'marker-symbol', 'A'] ] }); map.addLayer({ 'id': 'directions-origin-label', 'type': 'symbol', 'source': 'directions', 'layout': { 'text-field': 'A', 'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'], 'text-size': 12 }, 'paint': { 'text-color': '#fff' }, 'filter': [ 'all', ['in', '$type', 'Point'], ['in', 'marker-symbol', 'A'] ] }); map.addLayer({ 'id': 'directions-destination-point', 'type': 'circle', 'source': 'directions', 'paint': { 'circle-radius': 18, 'circle-color': '#8a8bc9' }, 'filter': [ 'all', ['in', '$type', 'Point'], ['in', 'marker-symbol', 'B'] ] }); map.addLayer({ 'id': 'directions-destination-label', 'type': 'symbol', 'source': 'directions', 'layout': { 'text-field': 'B', 'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'], 'text-size': 12 }, 'paint': { 'text-color': '#fff' }, 'filter': [ 'all', ['in', '$type', 'Point'], ['in', 'marker-symbol', 'B'] ] }); }

// when you want to switch styles use the following 3 step process removeDirectionsLayers(); map.setStyle('mapbox://styles/mapbox/satellite-v9'); addDirectionsLayers();

jonathonwpowell commented 4 years ago

This seems due to the onAdd method in directions.js

    if (this._map.loaded()) this.mapState()
    else this._map.on('load', () => this.mapState());

If a style is changed, loaded() will return false while it is loading but the 'load' event only triggers the first time the map is loaded, so it will not trigger if the style is changed after the initial load.

Related issues: https://github.com/mapbox/mapbox-gl-js/issues/6707

111

https://github.com/mapbox/mapbox-gl-js/issues/8691

jonathonwpowell commented 4 years ago

I think that the best solution may be subscribing to the idle event instead, and then unsubscribing once the event has triggered

xiaofanliang commented 1 year ago

It is 2023! And this issue still persists... reloading the data layer after the style change works but only for displaying static data. The Direction Plugin API will still return the same error.

The layer 'directions-route-line-alt' does not exist in the map's style and cannot be queried for features.

The markers for origin and destination and the routes disappear on the map, even though the API picks up the coordinates correctly and returns the right instructions and route statistics.

Here is a simple snippet to recreate this bug if needed. I am desperately searching for workarounds. I tried removing the plugin and then re-creating it after the style change (hoping it will reinitialize the plugin), but it still does not work.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Display navigation directions</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
#menu {
position: absolute;
background: #efefef;
padding: 10px;
left: 500px;
font-family: 'Open Sans', sans-serif;
}
</style>
</head>
<body>
<script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-directions/v4.1.1/mapbox-gl-directions.js"></script>
<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-directions/v4.1.1/mapbox-gl-directions.css" type="text/css">
<div id="map">
</div>
<div id="menu">
<div id="directions"></div>
<input id="satellite-streets-v12" type="radio" name="rtoggle" value="satellite" checked="checked">
<!-- See a list of Mapbox-hosted public styles at -->
<!-- https://docs.mapbox.com/api/maps/styles/#mapbox-styles -->
<label for="satellite-streets-v12">satellite streets</label>
<input id="light-v11" type="radio" name="rtoggle" value="light">
<label for="light-v11">light</label>
</div>

<script>
<script>
    mapboxgl.accessToken = ''
    const map = new mapboxgl.Map({
        container: 'map',
        style: 'mapbox://styles/mapbox/streets-v12',
        center: [-79.4512, 43.6568],
        zoom: 13
    });

    map.addControl(
    new MapboxDirections({
        accessToken: mapboxgl.accessToken
    }),
    'top-left'
    );

    const layerList = document.getElementById('menu');
    const inputs = layerList.getElementsByTagName('input');

    for (const input of inputs) {
        input.onclick = (layer) => {
            const layerId = layer.target.id;
            map.setStyle('mapbox://styles/mapbox/' + layerId);
        };
    }
</script>
</script>

</body>
</html>