98

I have a page where given a select to the user he can switch the leaflet map I show.

After a initial leaflet map load, my problem is when i want to refresh the map.

I always get "Map container is already initialized":

The problem line is:

var map = L.map('mapa').setView([lat, lon], 15);

Initially it loads well, but when I select another parameter in the form and want to display the map another time it crashes.

btw, I've tried to destroy and recreate $('#mapa') with jQuery before the second setView() but it shows the same error.

Hugo
  • 27,885
  • 8
  • 82
  • 98
leandro713
  • 1,268
  • 1
  • 10
  • 17
  • I've seen a similar error with `nuxt-leaflet` during unit testing with `Jest`, but can't reproduce it anymore. Based on the test, I could solve it by creating a dummyDiv ``` const dummyDiv: HTMLDivElement = global.document.createElement("div"); const dummyID: string = "dummyID"; dummyDiv.setAttribute("id", dummyID); global.document.body.appendChild(dummyDiv);` ``` then in `shallowMount` attaching it: ``` wrapper = shallowMount(MapLeaflet, { attachTo: dummyDiv, localVue, store, stubs: { NuxtLink: RouterLinkStub, }, }); ``` – Daniel Danielecki Dec 06 '22 at 11:13

26 Answers26

127

Try map.remove(); before you try to reload the map. This removes the previous map element using Leaflet's library (instead of jquery's).

Josh
  • 3,385
  • 5
  • 23
  • 45
55

the best way

map.off();
map.remove();

You should add map.off(), it also works faster, and does not cause problems with the events

yit770
  • 559
  • 4
  • 4
  • 5
    I'm sorry how do you use this ? – djack109 Aug 22 '17 at 10:37
  • @djack109 Keep a reference of the map handler object. You can get this from the onReady event from the map. Then you can call these two methods on that handler object. If that's not clear I'm happy to provide an example. – Jack Nov 13 '19 at 13:19
  • Be sure that map is a global var, so you can check if the map has been set previously. Use truthy: ```if(map) {``` then, set ```map = map.off();``` and ```map = map.remove();``` With this approach, there's no need to reset div containers innerHTML. – like2think Jun 17 '22 at 15:05
43

Html:

<div id="weathermap"></div>

JavaScript:

function buildMap(lat,lon)  {
    document.getElementById('weathermap').innerHTML = "<div id='map' style='width: 100%; height: 100%;'></div>";
    var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
                    osmAttribution = 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors,' +
                        ' <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
    osmLayer = new L.TileLayer(osmUrl, {maxZoom: 18, attribution: osmAttribution});
    var map = new L.Map('map');
    map.setView(new L.LatLng(lat,lon), 9 );
    map.addLayer(osmLayer);
    var validatorsLayer = new OsmJs.Weather.LeafletLayer({lang: 'en'});
    map.addLayer(validatorsLayer);
}

I use this:

document.getElementById('weathermap').innerHTML = "<div id='map' style='width: 100%; height: 100%;'></div>";

to reload content of div where render map.

Artem Kovalov
  • 1,102
  • 11
  • 10
24

Before initializing map check for is the map is already initiated or not

var container = L.DomUtil.get('map');
      if(container != null){
        container._leaflet_id = null;
      }
Dipin Raj C
  • 390
  • 3
  • 11
13

Only use this

map.invalidateSize();

https://github.com/Leaflet/Leaflet/issues/690

Mahdi Bashirpour
  • 17,147
  • 12
  • 117
  • 144
5

well, after much seeking i realized it's well documented at http://leafletjs.com/examples/layers-control.html

i've ended not repainting the map, but print it once and repaint the points on each new ajax call, so the problem was how to clean up the old points and print only the new ones. i've ended doing this:

var point = L.marker([new_marker[0], new_marker[1]]).addTo(map).bindPopup('blah blah');
points.push(point); 
//points is a temporary array where i store the points for removing them afterwards

so, at each new ajax call, before painting the new points, i do the following:

for (i=0;i<points.length;i++) {
  map.removeLayer(points[i]);
}
points=[];

so far, so good :-)

leandro713
  • 1,268
  • 1
  • 10
  • 17
5

When you just remove a map, it destroys the div id reference, so, after remove() you need to build again the div where the map will be displayed, in order to avoid the "Uncaught Error: Map container not found".

if(map != undefined || map != null){
    map.remove();
   $("#map").html("");
   $("#preMap").empty();
   $( "<div id=\"map\" style=\"height: 500px;\"></div>" ).appendTo("#preMap");
}
Damico
  • 1,107
  • 10
  • 5
4

What you can try is to remove the map before initialising it or when you leave the page:

if(this.map) {
  this.map.remove();
}
4

You should try to unmount the function in react js to remove the existing map.

const Map = () => {

    const mapContainer = useRef();
    const [map, setMap] = useState({});

    useEffect(()=>{
        const map = L.map(mapContainer.current, {attributionControl: false}).setView([51.505, -0.09], 13);

    L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
        maxZoom: 18,
        attribution: 'Map',
        id: 'mapbox/streets-v11',
        tileSize: 512,
        zoomOffset: -1
    }).addTo(map);

    // unmount map function
    return () => map.remove();
    }, []);

    return (
        <div style={{padding: 0, margin: 0, width: "100%", height: "100vh",}}
             ref={el => mapContainer.current = el}>
        </div>
    );
}
  • Especially running in development mode, useEffect is called twice. which was causing the issue. A cleanup function solves it. If using typescript, map.remove() can be put inside {}. – nurp Aug 01 '23 at 13:22
3

I had the same problem on angular when switching page. I had to add this code before leaving the page to make it works:

    $scope.$on('$locationChangeStart', function( event ) {
    if(map != undefined)
    {
      map.remove();
      map = undefined
      document.getElementById('mapLayer').innerHTML = "";
    }
});

Without document.getElementById('mapLayer').innerHTML = "" the map was not displayed on the next page.

Dan
  • 1,159
  • 13
  • 8
  • This helped me. I am using Angular 6 and changing the map depending on locations the user clicks on. I just have a method to create a new map which return the map object. When I update the map, I pass the existing map object in and do the above without the innerHTML part... – Kevin van Zyl May 21 '19 at 12:52
  • This method works, however, I don't really understand what different `map.remove()` and `map = undefined` statements do – Sapinder Singh Sep 14 '20 at 09:24
  • Where it's suposed to go this code? I have a component which have the `
    ` And in the .ts I init the map like this: `this.map = L.map('mapDetails', { center: [40.4379543,-3.6795367], zoom: 6 }); const tiles = L.tileLayer( 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, minZoom: 6, attribution: '© OpenStreetMap' } ); tiles.addTo(this.map);`
    – Emili Bellot Pulido Nov 15 '22 at 11:11
3

if you want update map view, for example change map center, you don’t have to delete and then recreate the map, you can just update coordinate

const mapInit = () => {
 let map.current = w.L.map('map');

 L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://osm.org/copyright" target="_blank">OpenStreetMap</a> contributors'
 }).addTo(map.current);
}

const setCoordinate = (gps_lat, gps_long) => {
  map.setView([gps_lat, gps_long], 13);
}

initMap();

setCoordinate(50.403723 30.623538);

setTimeout(() => {
  setCoordinate(51.505, -0.09);
}, 3000);
Роман
  • 479
  • 6
  • 7
2

For refreshing map in same page you can use below code to create a map on the page

if (!map) {
    this.map = new L.map("mapDiv", {
        center: [24.7136, 46.6753],
        zoom: 5,
        renderer: L.canvas(),
        attributionControl: true,
    });
}

then use below line to refresh the map, but make sure to use same latitude, longitude and zoom options

map.setView([24.7136, 46.6753], 5);  

Also, I had the same issue when switching between tabs in the same page using angular 2+, and I was able to fix it by adding below code in Component constructor

var container = L.DomUtil.get('mapDiv');
if (container != null) {
    container.outerHTML = ""; // Clear map generated HTML
    // container._leaflet_id = null; << didn't work for me
}
ElasticCode
  • 7,311
  • 2
  • 34
  • 45
1

I had same problem.then i set globally map variable e.g var map= null and then for display map i check

if(map==null)then map=new L.Map('idopenstreet').setView();

By this solution your map will be initialize only first time after that map will be fill by L.Map then it will not be null. so no error will be there like map container already initialize.

kaiser
  • 21,817
  • 17
  • 90
  • 110
shaishav shukla
  • 348
  • 6
  • 11
1

set

    var container = L.DomUtil.get('map');
    if (container && container['_leaflet_id'] != null) {
      container.remove();
    }

before var map = L.map('map')

enjoy :)

Kalnode
  • 9,386
  • 3
  • 34
  • 62
mahdi
  • 189
  • 2
  • 2
  • 11
0

use the redrawAll() function rather than renderAll().

Dilan
  • 185
  • 1
  • 9
0

We facing this issue today and we solved it. what we do ?

leaflet map load div is below.

<div id="map_container">
   <div id="listing_map" class="right_listing"></div>
</div>

When form input change or submit we follow this step below. after leaflet map container removed in my page and create new again.

$( '#map_container' ).html( ' ' ).append( '<div id="listing_map" class="right_listing"></div>' );

After this code my leaflet map is working fine with form filter to reload again.

Thank you.

0

If you don't globally store your map object reference, I recommend

if (L.DomUtil.get('map-canvas') !== undefined) { 
   L.DomUtil.get('map-canvas')._leaflet_id = null; 
}

where <div id="map-canvas"></div> is the object the map has been drawn into.

This way you avoid recreating the html element, which would happen, were you to remove() it.

Joe Eifert
  • 1,306
  • 14
  • 29
0

For refresh leaflet map you can use this code:

this.map.fitBounds(this.map.getBounds());

miko866
  • 182
  • 2
  • 9
0

I had the same problem on react I solved it by initialized at the top in useEffect Here is my React Code.

const mapContainerRef = useRef(null);

useEffect( async () => {
  const res =await Axios.get(BASE_PATH + 'fetchProperty')

  const container = L.DomUtil.get(mapContainerRef.current); if(container != null){ container._leaflet_id = null; }

  if(container) {


  const mapView = L.map( mapContainerRef.current, {
    zoom: 13,
    center: [19.059984, 72.889999]
    //  maxZoom: 13
    // minZoom: 15
  });
  // const canvas = mapView.getCanvasContainer();
  mapView.zoomControl.setPosition("bottomright");
  mapView.attributionControl.addAttribution(
    "<a href='https://mascots.pro'>Mascots. pro</a>"
  );
  L.tileLayer(
    // "https://api.mapbox.com/styles/v1/mapbox/dark-v9/tiles/{z}/{x}/{y}?access_token=" + https://api.mapbox.com/styles/v1/anonymousmw/cko1eb1r20mdu18qqtps8i03p/tiles/{z}/{x}/{y}?access_token=
    "https://api.mapbox.com/styles/v1/mapbox/dark-v9/tiles/{z}/{x}/{y}?access_token=" +
      access_token,
    {
      attribution: '<a href="http://mascots.work">Mascots</a>'
    }
  ).addTo(mapView);

  const mask = L.tileLayer.mask(
    "https://api.mapbox.com/styles/v1/anonymousmw/cko1eb1r20mdu18qqtps8i03p/tiles/{z}/{x}/{y}?access_token=" +
      access_token,
    {
      attribution: '<a href="https://mascots.pro">Mascots pro</a>',
      maskSize: 300
      // maxZoom: 18,
      // maxNativeZoom: 16
      // tms: true
    }
  )
  .addTo(mapView);

  mapView.on("mousemove", function (e) {
    mask.setCenter(e.containerPoint);
  });
  res.data.map((marker) => {
  
    const innerHtmlContent = `<div id='popup-container' class='popup-container'> <h3> Property Details</h3>
    <div class='popup-label'>Building Name :<p>${marker.Building}</p></div>
    <div class='popup-address-label'> Address : <p>${marker.Landmark}, ${marker.Location}</p></div>
    <div class='popup-rent-label'>Monthly Rent : <p> ₹ ${marker.Price}</p></div>
    </div>`;
    const divElement = document.createElement("div");
    const assignBtn = document.createElement("div");
    assignBtn.className = "map-link";
    assignBtn.innerHTML = `<button class="view-btn">View Property</button>`;
    divElement.innerHTML = innerHtmlContent;
    divElement.appendChild(assignBtn);
    assignBtn.addEventListener("click", (e) => {
      console.log("dsvsdvb");
    });
    var iconOptions = {
      iconUrl: "/images/location_pin2.svg",
      iconSize: [25, 25]
    };
    var customIcon = L.icon(iconOptions);

    // create popup contents
    var customPopup = divElement;

    // specify popup options
    var customOptions = {
      maxWidth: "500",
      className: "custom"
    };

    const markerOptions = {
      // title: "MyLocation",
      //    draggable: true
      clickable: true,
      icon: customIcon
    };
    const mark = L.marker([marker.Latitude,marker.Longitude], markerOptions);
    mark.bindPopup(customPopup, customOptions);
    mark.addTo(mapView);
    // return mapView.off();
   
  });
  return () => mapView.remove();
}
}, [])

return (
  <div className="map-box">
        <div className="map-container" ref={mapContainerRef}></div>
    </div>
);
Jerry
  • 30
  • 1
0

I went through the same problem, so I created a method inside the map instance to reload it.

var map = L.map('mapa').setView([lat, lon], 15);
map.reload = function(){
   map.remove();
   map = L.map('mapa').setView([lat, lon], 15);
}

....

map.reload();

0

I did this in reactjs

// Create map (dev = reuse existing map)
let myMap = L.DomUtil.get('map');
if(myMap == null){
  myMap = L.map('mapid').setView(currentLocation, zoom);
}
Peter F
  • 420
  • 4
  • 12
0

Html:

<div id='leaflet-map' #leafletMap></div>

JavaScript:

@ViewChild('leafletMap')
private mapElement: ElementRef;

private initMap(): void {
   this.map = leaflet.map(this.mapElement.nativeElement, {
       center: [39.01860177826393, 35.30274319309024],
       zoom: 4,
   });

   leaflet
   .tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '',
      maxZoom: 18,
   })
   .addTo(this.map);
}
metodribic
  • 1,561
  • 17
  • 27
D.Y. Aksu
  • 36
  • 7
0

My hacky implementation to refresh the map was to use:

  // Hack to refresh the map by panning by zero
  public refreshMap() {
    this.map.panBy([0,0]);
  }
Rossco
  • 3,563
  • 3
  • 24
  • 37
0

In case you're working with NextJs and typescrypt, what worked for me was

container._leaflet_id = null;

as someone proposed, but had some typing erros so my approach is

const L = await import('leaflet');
  const container = L.DomUtil.get('map');
  if (!container) return;
  if (container.classList.contains('leaflet-container')) return;
  const map = L.map('map', {
    center: [19.434817, -99.1268643],
    zoom: 18,
  });
  map.invalidateSize();
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
  }).addTo(map);
  const marker = L.marker([19.434786638353515, -99.1268643025101]).addTo(
    map
  );

after this code, (all these code needs to be inside a useEffect hook), leaflet just worked fine.

0

Different things to try when troubleshooting "Map container is already initialized". Keep in mind all of my usage has been web app context.

(A) Remove the map if it's already initialized

if (map != undefined) map.remove()

(B) Use a unique ID or key

On your map container, use a key that will invoke a fresh rendering: e.g. in React, you may try: key={new Date().getTime()}

Or, use a unique id

For example, in Vue I do this:

<div :id="id"></div>

data() {
    return {
        id: 'mapLeaflet-'+Date.now()
    }
}

And anywhere I refer to the DOM element, I use this.id:

let mapHTMLContainer = document.getElementById(this.id)

(C) Before map init, check existence of map first

Obviously, if you have other trouble, do some hard checks.

In Vue, I do this:

methods: {

    initMap() {
        let mapHTMLContainer = document.getElementById(this.id)

        if (mapHTMLContainer
            && mapHTMLContainer.hasChildNodes() == false
            && !this.map) {

             // DO STUFF
             // e.g. initialize leaflet, set tile layer, add markers, etc

        }
        
    }
}

(D) On map init, pass the actual HTML element, not just a string

When initiating Leaflet map, pass the actual HTML element instead of just the string id.

Both methods are officially supported, but I cannot find any information talking about the differences. https://leafletjs.com/reference.html#map-l-map

BEFORE

This works in general and is convenient...

this.map = L.map("mymap")

AFTER

... but, instead try to actually get the element, and pass it. I observed a difference doing this.

let myMapElement = document.getElementById(this.id)
this.map = L.map(myMapElement)

(E) On component "dismount" destroy things

If you have a dismount state, use it and destroy stuff related to your map component. Make sure things don't hang-around.

In Vue, I do this:

beforeDestroy() {

    // Destroy any listeners
    this.$nuxt.$off('refreshMap') // Nuxt is the framework I use

    // Clear the map instance in your component
    if (this.map) {
        // I haven't fully tested these; are they valid?
        this.map.off() // Leaflet function
        this.map.remove() // Leaflet function
        this.map = null // null it for good measure
    }

    // Clear out the HTML container of any children, just in case
    let mapHTMLElement = document.getElementById(this.id)
    if (mapHTMLElement) {
        mapHTMLElement.outerHTML = ""
    }

}
Kalnode
  • 9,386
  • 3
  • 34
  • 62
0

My solution can be termed as a hack, but here it is -

I structured my map divs like this -

<div id='map'>

<div id='inmap'></div>

</div>

Now I use a leaflet provider map as a placeholder attested in the inmap instead of map. When the user request the temperature in my app, the new map just overlays on the top of the map(because it is attached to map).