Cable Monkey
Cable Monkey

Reputation: 13

Leaflet - Changing All Layers

Something changed between Leaflet 1.0.3 and 1.1.0 that makes it where I can no longer dynamically remove all layers on a map and then add a new set of layers in their place.

To add a set of waypoints and the base map (single image):

function drawMap() {
  waypointLayer = L.layerGroup([]);
  var bounds = [[0, 0], [30, 30]];

  L.imageOverlay(
    "https://www.dropbox.com/s/r098ggmze3763fx/examplemap.png?raw=1",
    bounds
  ).addTo(ourMap);
  ourMap.fitBounds(bounds);

  for (var wp of waypoints[showPoints]) {
    waypointLayer.addLayer(
      L.rectangle([[wp[0] + 0.2, wp[1] - 0.2], [wp[0] - 0.2, wp[1] + 0.2]], {
        color: "black",
        fillColor: "black",
        fillOpacity: 0.9
      })
    );
  }
  ourMap.addLayer(waypointLayer);
}

To erase all layers, I'm using:

  ourMap.eachLayer(function(thisLayer) {
    ourMap.removeLayer(thisLayer);
  });

I've set up a codepen with Leaflet 1.2.0 at https://codepen.io/cablemonkey42/pen/wpayaX.

For a working 1.0.3 example using the same exact code: https://codepen.io/cablemonkey42/pen/PEqRyq

Other than my probably obvious novice skills, can anyone please point me to what I'm doing wrong? Thanks

Upvotes: 1

Views: 1019

Answers (1)

ghybs
ghybs

Reputation: 53185

Looks like there is indeed a regression between Leaflet version 1.1.0+ and 1.0.3-, triggered by the fact that you remove the SVG renderer layer (that is automatically added by Leaflet) onto which your Rectangles are drawn.

You have several "easy" workarounds for the time being:

  1. Do not blindly remove all layers from the map, but only those that you have explicitly added, in order to preserve the SVG renderer layer intact.
$("#mapset").change(function() {
  //Erase everything shown
  /*ourMap.eachLayer(function(thisLayer) {
    ourMap.removeLayer(thisLayer);
  });*/

  /////////////////////////////////////////////////////////////
  // Erase only the explicitly added layers,
  // in order to preserve automatically managed layers (Renderer).
  if (imageOverlay) {
    imageOverlay.remove();
  }
  if (waypointLayer) {
    waypointLayer.remove();
  }
  /////////////////////////////////////////////////////////////

  //assign showPoints to whichever waypoint set (0 or 1) we want to show
  showPoints = $(this).val();

  //now draw it...
  drawMap();
});

var ourMap; //LeafletJS Map
var waypointLayer; //LayerGroup of Waypoints

/////////////////////////////////////////////////////////////
var imageOverlay; // Reference to explicitly added layer.
/////////////////////////////////////////////////////////////

//The variable "waypoints" is two different sets of waypoints.
//Which waypoint shown is defined by "showPoints".
var waypoints = [];
waypoints[0] = [
  [5, 5],
  [10, 5],
  [20, 20]
];
waypoints[1] = [
  [25, 25],
  [20, 15],
  [10, 10]
];
var showPoints = 0;

$(function() {
  //Initialize map and draw default data
  ourMap = L.map("ourmap", {
    crs: L.CRS.Simple,
    minZoom: 3,
    maxZoom: 10,
    zoomDelta: 1,
    zoomSnap: 0.01
  });
  drawMap();
});


$("#mapset").change(function() {
  //Erase everything shown
  /*ourMap.eachLayer(function(thisLayer) {
    ourMap.removeLayer(thisLayer);
  });*/

  /////////////////////////////////////////////////////////////
  // Erase only the explicitly added layers,
  // in order to preserve automatically managed layers (Renderer).
  if (imageOverlay) {
    imageOverlay.remove();
  }
  if (waypointLayer) {
    waypointLayer.remove();
  }
  /////////////////////////////////////////////////////////////

  //assign showPoints to whichever waypoint set (0 or 1) we want to show
  showPoints = $(this).val();

  //now draw it...
  drawMap();
});

function drawMap() {
  waypointLayer = L.layerGroup([]);
  var bounds = [
    [0, 0],
    [30, 30]
  ];

  //assign a base map (blank tan-ish map for demo, normally I would change based on data set)

  /////////////////////////////////////////////////////////////
  // Keep a reference to each explicitly added layer.
  imageOverlay = L.imageOverlay(
    "https://www.dropbox.com/s/r098ggmze3763fx/examplemap.png?raw=1",
    bounds
  ).addTo(ourMap);
  /////////////////////////////////////////////////////////////

  //zoom to show the entire base map image
  ourMap.fitBounds(bounds);

  //add each waymark of our selected set to a layergroup
  for (var wp of waypoints[showPoints]) {
    waypointLayer.addLayer(
      L.rectangle([
        [wp[0] + 0.2, wp[1] - 0.2],
        [wp[0] - 0.2, wp[1] + 0.2]
      ], {
        color: "black",
        fillColor: "black",
        fillOpacity: 0.9
      })
    );
  }

  //add the layergroup to the map
  ourMap.addLayer(waypointLayer);
}
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
<script src="https://unpkg.com/[email protected]/dist/leaflet-src.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>

Waypoint Set:
<select id="mapset">
    <option value="0" selected>0</option>
    <option value="1">1</option>
  </select>
<div id="ourmap" style="height: 170px"></div>

  1. Skip the Renderers, i.e. those that are instance of L.Renderer.
$("#mapset").change(function() {
  //Erase everything shown
  ourMap.eachLayer(function(thisLayer) {

    /////////////////////////////////////////////////////////////
    // Skip Renderer type layers.
    if (thisLayer instanceof L.Renderer) {
      return;
    }
    /////////////////////////////////////////////////////////////

    ourMap.removeLayer(thisLayer);
  });

  //assign showPoints to whichever waypoint set (0 or 1) we want to show
  showPoints = $(this).val();

  //now draw it...
  drawMap();
});

var ourMap; //LeafletJS Map
var waypointLayer; //LayerGroup of Waypoints

//The variable "waypoints" is two different sets of waypoints.
//Which waypoint shown is defined by "showPoints".
var waypoints = [];
waypoints[0] = [
  [5, 5],
  [10, 5],
  [20, 20]
];
waypoints[1] = [
  [25, 25],
  [20, 15],
  [10, 10]
];
var showPoints = 0;

$(function() {
  //Initialize map and draw default data
  ourMap = L.map("ourmap", {
    crs: L.CRS.Simple,
    minZoom: 3,
    maxZoom: 10,
    zoomDelta: 1,
    zoomSnap: 0.01
  });
  drawMap();
});


$("#mapset").change(function() {
  //Erase everything shown
  ourMap.eachLayer(function(thisLayer) {

    /////////////////////////////////////////////////////////////
    // Skip Renderer type layers.
    if (thisLayer instanceof L.Renderer) {
      return;
    }
    /////////////////////////////////////////////////////////////

    ourMap.removeLayer(thisLayer);
  });

  //assign showPoints to whichever waypoint set (0 or 1) we want to show
  showPoints = $(this).val();

  //now draw it...
  drawMap();
});

function drawMap() {
  waypointLayer = L.layerGroup([]);
  var bounds = [
    [0, 0],
    [30, 30]
  ];

  //assign a base map (blank tan-ish map for demo, normally I would change based on data set)
  L.imageOverlay(
    "https://www.dropbox.com/s/r098ggmze3763fx/examplemap.png?raw=1",
    bounds
  ).addTo(ourMap);

  //zoom to show the entire base map image
  ourMap.fitBounds(bounds);

  //add each waymark of our selected set to a layergroup
  for (var wp of waypoints[showPoints]) {
    waypointLayer.addLayer(
      L.rectangle([
        [wp[0] + 0.2, wp[1] - 0.2],
        [wp[0] - 0.2, wp[1] + 0.2]
      ], {
        color: "black",
        fillColor: "black",
        fillOpacity: 0.9
      })
    );
  }

  //add the layergroup to the map
  ourMap.addLayer(waypointLayer);
}
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
<script src="https://unpkg.com/[email protected]/dist/leaflet-src.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>

Waypoint Set:
<select id="mapset">
    <option value="0" selected>0</option>
    <option value="1">1</option>
  </select>
<div id="ourmap" style="height: 170px"></div>

  1. Use Canvas Renderer instead of default SVG Renderer (with preferCanvas map option). The former seems immune to this bug.
$(function() {
  //Initialize map and draw default data
  ourMap = L.map("ourmap", {
    crs: L.CRS.Simple,
    minZoom: 3,
    maxZoom: 10,
    zoomDelta: 1,
    zoomSnap: 0.01,

    /////////////////////////////////////////////////////////////
    preferCanvas: true // Use Canvas that is immune to SVG Renderer bug.
    /////////////////////////////////////////////////////////////
  });
  drawMap();
});

var ourMap; //LeafletJS Map
var waypointLayer; //LayerGroup of Waypoints

//The variable "waypoints" is two different sets of waypoints.
//Which waypoint shown is defined by "showPoints".
var waypoints = [];
waypoints[0] = [
  [5, 5],
  [10, 5],
  [20, 20]
];
waypoints[1] = [
  [25, 25],
  [20, 15],
  [10, 10]
];
var showPoints = 0;

$(function() {
  //Initialize map and draw default data
  ourMap = L.map("ourmap", {
    crs: L.CRS.Simple,
    minZoom: 3,
    maxZoom: 10,
    zoomDelta: 1,
    zoomSnap: 0.01,

    /////////////////////////////////////////////////////////////
    preferCanvas: true // Use Canvas that is immune to SVG Renderer bug.
    /////////////////////////////////////////////////////////////
  });
  drawMap();
});


$("#mapset").change(function() {
  //Erase everything shown
  ourMap.eachLayer(function(thisLayer) {
    ourMap.removeLayer(thisLayer);
  });

  //assign showPoints to whichever waypoint set (0 or 1) we want to show
  showPoints = $(this).val();

  //now draw it...
  drawMap();
});

function drawMap() {
  waypointLayer = L.layerGroup([]);
  var bounds = [
    [0, 0],
    [30, 30]
  ];

  //assign a base map (blank tan-ish map for demo, normally I would change based on data set)
  L.imageOverlay(
    "https://www.dropbox.com/s/r098ggmze3763fx/examplemap.png?raw=1",
    bounds
  ).addTo(ourMap);

  //zoom to show the entire base map image
  ourMap.fitBounds(bounds);

  //add each waymark of our selected set to a layergroup
  for (var wp of waypoints[showPoints]) {
    waypointLayer.addLayer(
      L.rectangle([
        [wp[0] + 0.2, wp[1] - 0.2],
        [wp[0] - 0.2, wp[1] + 0.2]
      ], {
        color: "black",
        fillColor: "black",
        fillOpacity: 0.9
      })
    );
  }

  //add the layergroup to the map
  ourMap.addLayer(waypointLayer);
}
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
<script src="https://unpkg.com/[email protected]/dist/leaflet-src.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>

Waypoint Set:
<select id="mapset">
    <option value="0" selected>0</option>
    <option value="1">1</option>
  </select>
<div id="ourmap" style="height: 170px"></div>

You can also easily patch the SVG Renderer code:

L.SVG.include({
  _destroyContainer: function() {
    L.DomUtil.remove(this._container);
    L.DomEvent.off(this._container);
    delete this._container;
    delete this._rootGroup;

    // Make sure to also clear the cache for svgSize,
    // so that next container width and height will be set.
    delete this._svgSize;
  }
});

/////////////////////////////////////////////////////////////
// Patch SVG Renderer code to remove regression bug.
L.SVG.include({
  _destroyContainer: function() {
    L.DomUtil.remove(this._container);
    L.DomEvent.off(this._container);
    delete this._container;
    delete this._rootGroup;

    // Make sure to also clear the cache for svgSize,
    // so that next container width and height will be set.
    delete this._svgSize;
  }
});
/////////////////////////////////////////////////////////////

var ourMap; //LeafletJS Map
var waypointLayer; //LayerGroup of Waypoints

//The variable "waypoints" is two different sets of waypoints.
//Which waypoint shown is defined by "showPoints".
var waypoints = [];
waypoints[0] = [
  [5, 5],
  [10, 5],
  [20, 20]
];
waypoints[1] = [
  [25, 25],
  [20, 15],
  [10, 10]
];
var showPoints = 0;

$(function() {
  //Initialize map and draw default data
  ourMap = L.map("ourmap", {
    crs: L.CRS.Simple,
    minZoom: 3,
    maxZoom: 10,
    zoomDelta: 1,
    zoomSnap: 0.01
  });
  drawMap();
});


$("#mapset").change(function() {
  //Erase everything shown
  ourMap.eachLayer(function(thisLayer) {
    ourMap.removeLayer(thisLayer);
  });

  //assign showPoints to whichever waypoint set (0 or 1) we want to show
  showPoints = $(this).val();

  //now draw it...
  drawMap();
});

function drawMap() {
  waypointLayer = L.layerGroup([]);
  var bounds = [
    [0, 0],
    [30, 30]
  ];

  //assign a base map (blank tan-ish map for demo, normally I would change based on data set)
  L.imageOverlay(
    "https://www.dropbox.com/s/r098ggmze3763fx/examplemap.png?raw=1",
    bounds
  ).addTo(ourMap);

  //zoom to show the entire base map image
  ourMap.fitBounds(bounds);

  //add each waymark of our selected set to a layergroup
  for (var wp of waypoints[showPoints]) {
    waypointLayer.addLayer(
      L.rectangle([
        [wp[0] + 0.2, wp[1] - 0.2],
        [wp[0] - 0.2, wp[1] + 0.2]
      ], {
        color: "black",
        fillColor: "black",
        fillOpacity: 0.9
      })
    );
  }

  //add the layergroup to the map
  ourMap.addLayer(waypointLayer);
}
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
<script src="https://unpkg.com/[email protected]/dist/leaflet-src.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>

Waypoint Set:
<select id="mapset">
    <option value="0" selected>0</option>
    <option value="1">1</option>
  </select>
<div id="ourmap" style="height: 170px"></div>

Upvotes: 1

Related Questions