Leaflet: L.Icon.Default brings a wrong image url

Created on 28 Sep 2016  ·  90Comments  ·  Source: Leaflet/Leaflet

  • [ x] I'm reporting a bug, not asking for help
  • [ ] I've looked at the documentation to make sure the behaviour is documented and expected
  • [x ] I'm sure this is a Leaflet code issue, not an issue with my own code nor with the framework I'm using (Cordova, Ionic, Angular, React…)
  • [ ] I've searched through the issues to make sure it's not yet reported

The image url leaflet prsents me is https://uismedia.geo-info-manager.com/apis/leaflet_1/imagesmarker-icon-2x.png. It seems there is a missing "/"
Additionally i have an error
leaflet.min.js:5 GET https://uismedia.geo-info-manager.com/apis/leaflet_1/images/ 403 (Forbidden)

compatibility

Most helpful comment

Wanted to share what I did to workaround the invalid data:url issue. Basically set the default icon to a new one that uses the provided marker icon and shadow, but will let webpack handle the data encoding of those images individually. Just include a file like this somewhere.

import L from 'leaflet';

import icon from 'leaflet/dist/images/marker-icon.png';
import iconShadow from 'leaflet/dist/images/marker-shadow.png';

let DefaultIcon = L.icon({
    iconUrl: icon,
    shadowUrl: iconShadow
});

L.Marker.prototype.options.icon = DefaultIcon;

Could probably be tweaked to include the retina icon as well.

All 90 comments

  • Is there any public webpage in your server we can visit, so we can reproduce the problem ourselves?
  • What OS and web browser are you using?

leaflet.min.js:5 GET https://uismedia.geo-info-manager.com/apis/leaflet_1/images/ 403 (Forbidden)

This may be from the same issue discussed in https://github.com/Leaflet/Leaflet/issues/4849

Indeed. Which why I'm curious as to know the circumstances this can be reproduced, so we can make unit tests against those.

I had the same issue moving from RC3 to 1.0.1 - in my code I had the line L.Icon.Default.imagePath = 'images'; - can't remember quite why this was, but commenting it out solved the issue - might be worth checking you don't have similar

suddenly got the same issue in two completely different projects, both using webpack and leaflet.
If I add markers to the map, the images aren't found. Browser throws this error:
image

ok I got something.

So a marker looks like this currently because the images (icon and shadow) aren't found.
image

this function in leaflet:

_getIconUrl: function (name) {
    if (!L.Icon.Default.imagePath) {    // Deprecated, backwards-compatibility only
        L.Icon.Default.imagePath = this._detectIconPath();
    }

    // @option imagePath: String
    // `L.Icon.Default` will try to auto-detect the absolute location of the
    // blue icon images. If you are placing these images in a non-standard
    // way, set this option to point to the right absolute path.
    return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
},

causes the data:image urls to have the following string at the end of the url:
")marker-icon-2x.png.

The name of the file can be removed if you delete + L.Icon.prototype._getIconUrl.call(this, name). The ") part is probably from the _detectIconPath regex magic. I can't fix that so I just tried slicing the last two chars off, which results in this function:

_getIconUrl: function (name) {
    if (!L.Icon.Default.imagePath) {    // Deprecated, backwards-compatibility only
        L.Icon.Default.imagePath = this._detectIconPath();
    }

    // @option imagePath: String
    // `L.Icon.Default` will try to auto-detect the absolute location of the
    // blue icon images. If you are placing these images in a non-standard
    // way, set this option to point to the right absolute path.
  var url = (this.options.imagePath || L.Icon.Default.imagePath);

  return url.slice(0, - 2);
},

and here we go, the icon shows up. One last issue is that the shadow image is a marker icon as well - the src path is already wrong, I have no idea why (yet). So a marker now look like this:

image

@IvanSanchez does this help you a bit narrowing down the issue?

@codeofsumit would be nice to step through _detectIconPath and see what's going on there, especially what value does the path variable have before it's passed through the regex.

@IvanSanchez @perliedman I think I found the bug.

This is _detectIconPath

_detectIconPath: function () {
    var el = L.DomUtil.create('div',  'leaflet-default-icon-path', document.body);
    var path = L.DomUtil.getStyle(el, 'background-image') ||
               L.DomUtil.getStyle(el, 'backgroundImage');   // IE8
    document.body.removeChild(el);

    return path.indexOf('url') === 0 ?
        path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
}

the path variable is something like this:
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQ…n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=").

Which is correct.

Now the regex wants to extract the data.image url from this, and it returns this:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQ…n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=")

Notice the last ") that isn't removed from the path variable. The regex /^url\([\"\']?/ only targets url(" at the beginning of the path, not the ") at the end.
Using this regex works:

return path.indexOf('url') === 0 ?
    path.replace(/^url\([\"\']?/, '').replace(/\"\)$/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';

This fixes the image problem, together with

var url = (this.options.imagePath || L.Icon.Default.imagePath);

inside_getIconUrl. But it doesn't fix the shadow - I still don't know whats going on with the shadow.

having the same issue,
the path value in _detectIconPath is something like "url("data:image/png;base64,i...5CYII=")"
and it looks like _getIconUrl and _detectIconPath weren't designed to handle data URLs

the shadow issue seems to be cause by the fact that in _getIconUrl the value for L.Icon.Default.imagePath is already set with the marker data URL, so the image for marker is used and stretched
image

could it be related to this commit hardcoding the marker image?
https://github.com/Leaflet/Leaflet/commit/837d19093307eb5eeb1fca6536962a1ab1573dd5

@Radu-Filip I was able to fix it with these modifications to your PR - I'm not sure what other effects this might have though:
image

The problem is - as you said - that the default URL is the marker image, for all icons basically.
So first of all I added the default URL for the shadow to the CSS:

/* Default icon URLs */
.leaflet-default-icon-path {
    background-image: url(images/marker-icon.png);
}

.leaflet-default-shadow-path {
    background-image: url(images/marker-shadow.png);
}

Then I passed the name from _getIconUrl to _detectIconPath and used it to add the class to the element from which the path is extracted:

_getIconUrl: function (name) {

  L.Icon.Default.imagePath = this._detectIconPath(name);

    // @option imagePath: String
    // `L.Icon.Default` will try to auto-detect the absolute location of the
    // blue icon images. If you are placing these images in a non-standard
    // way, set this option to point to the right absolute path.
  var path = this.options.imagePath || L.Icon.Default.imagePath;
  return path.indexOf("data:") === 0 ? path : path + L.Icon.prototype._getIconUrl.call(this, name);
},

_detectIconPath: function (name) {
    var el = L.DomUtil.create('div',  'leaflet-default-' + name + '-path', document.body);
    var path = L.DomUtil.getStyle(el, 'background-image') ||
               L.DomUtil.getStyle(el, 'backgroundImage');   // IE8

    document.body.removeChild(el);

    return path.indexOf('url') === 0 ? path.replace(/^url\([\"\']?/, '').replace(/(marker-icon\.png)?[\"\']?\)$/, '') : '';
}

I also had to remove the if/else around detectIconPath, because it wasn't called for the shadow icon. Now everything works for the default marker icon - not sure about custom ones.

@codeofsumit yeah, i did something similar here https://github.com/Radu-Filip/Leaflet/tree/temp
and I'll use if for now as a hack. Hopefully there will be a more future proof solution down the line for those using webpack et al

@Radu-Filip do you mind updating your PR with that solution? It may get merged.

@codeofsumit done, let's see if it goes through

Hi,

Maybe I miss something, but it seems to me that this issue of Webpack building could be simply addressed with a Leaflet plugin, that would override L.Icon.Default behaviour.

Demo: http://playground-leaflet.rhcloud.com/nexo/1/edit?html,css,output

With this approach, you get rid of any tricky RegExp, and the default marker images are inlined (by hard coding), which is one of Webpack intended result for small images anyway.

A possible downside is that each marker has its own base64 icon, not sure if browsers can cache that… (same downside for PR #5041)
We could imagine a refinement by setting it as a background image instead of placing it in the image src attribute, as done for Layers Control icon for example.
There may be a hidden trap with this idea (edit: sounds like that one), otherwise I am sure it would have been implemented long time ago, as it would have avoided the image path detection in the first place.

Demo: http://playground-leaflet.rhcloud.com/mey/1/edit?html,css,output (not taking care of retina)

The biggest advantage of the plugin approach, is that it keeps this specific behaviour for Webpack projects only.

Hope this helps.

BTW, it seems to me that there is something intrinsically wrong here.

Leaflet makes a "complex" path detection to images, which must be in a pre-defined place compared to the CSS file.

But webpack build process bundles that CSS and may (or not) move the images as well (and rename them!), depending on what the developer requests to webpack (like requiring images).
Therefore the Leaflet detection surely fails when webpack is used.

PR #5041 is like a trick to accept the case where webpack inlines images into the CSS, at the cost of duplicating the Base64 image into each marker. Not even talking about the cost to non-webpack users.

PR #4979 was meant just to prevent the webpack build error message (due to missing file), it does not look to handle at all the actual image path resolution.
I guess developers then manually specify the L.Icon.Default.imagePath?
@jasongrout and @Eschon, maybe you could share how you managed it?

I don't really manage it. I just don't use the default icon so this bug wasn't a problem for me until now.

Hi, just a note to say I can reproduce this path error using the 1.0.1 version of this library.
I'm using it along with the leaflet Drupal module (7.x-1.x-dev), and here there is an issue reported to the module: https://www.drupal.org/node/2814039 in case its useful.

As far as I can see the "problem" is on _getIconUrl function? as after L.Icon.Default.imagePath there is a missing slash, so the image path for example in Drupal gets generated like this "/sites/all/libraries/leaflet/imagesmarker-icon.png". Between the images path and the marker image filename (marker-icon.png) should be a slash /.

Hi @anairamzap-mobomo,

Sounds like what you report is a different issue.

Unfortunately, as it seems to involve a port to a framework (Drupal), you would have first to make sure that the bug is not related to how that port is working.

Leaflet 1.0.x with vanilla JS correctly includes the trailing slash: http://playground-leaflet.rhcloud.com/fosa/1/edit?html,output

See for instance http://cgit.drupalcode.org/leaflet/tree/leaflet.module#n51, where L.Icon.Default.imagePath is overriden by the Drupal module.

Looks like that module does not handle the change between Leaflet 0.7.x and 1.0.x, where the slash must now be included in L.Icon.Default.imagePath.

As Leaflet 1.0.0 is a major release, I guess there is no commitment for backward compatibility.

hey @ghybs I see... I will contact the Drupal module maintainers to let them know this. As you said, it sounds that they have to modify the module to fit well with the 1.0.x version of the library, or at least add some lines to the docs warning about this.

Thanks for your feedback!

I'm having the exact same issue as originally reported -- in an aurulia skeleton/esnext+webpack project.

Until this is fixed I've copied the images out to my source folder and am using a custom marker -- omitting size/placement info seems to be ok...

        var customDefault = L.icon({
            iconUrl: 'images/marker-icon.png',
            shadowUrl: 'images/marker-shadow.png',
        });

Wanted to share what I did to workaround the invalid data:url issue. Basically set the default icon to a new one that uses the provided marker icon and shadow, but will let webpack handle the data encoding of those images individually. Just include a file like this somewhere.

import L from 'leaflet';

import icon from 'leaflet/dist/images/marker-icon.png';
import iconShadow from 'leaflet/dist/images/marker-shadow.png';

let DefaultIcon = L.icon({
    iconUrl: icon,
    shadowUrl: iconShadow
});

L.Marker.prototype.options.icon = DefaultIcon;

Could probably be tweaked to include the retina icon as well.

could anyone send the modifed leaflet.js file?
the code that @ajoslin103 is using :
```var customDefault = L.icon({
iconUrl: 'images/marker-icon.png',
shadowUrl: 'images/marker-shadow.png',
});

I did not modify the leaflet.js file, I just copied the marker images up out of the leaflet distribution into my normal images folder, and then used that fragment I posted as a custom icon.

I was unable to use crob611's solution because they were referenced in the original css as http and my site was being served via https.

``` function onLocationFound(e) {
var radius = e.accuracy / 2;
var customDefault = L.icon({
iconUrl: 'marker_icon_2x',
shadowUrl: 'marker_shadow.png'
});
L.marker(e.latlng).addTo(map)
.bindPopup("You are within " + radius + " meters from this point").openPopup();
L.circle(e.latlng, radius).addTo(map);
}

how can I set the new icon ?

I create the custom icon in my constructor (I'm using the Aurelia framework)

        this.customDefault = L.icon({
            iconUrl: 'images/marker-icon.png',
            shadowUrl: 'images/marker-shadow.png',
        });

Then I use it when I add the marker in the attached() method

        var map = L.map('mapid').setView([latitude, longitude], 13);
        let urlTemplate = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png';
        map.addLayer(L.tileLayer(urlTemplate, { minZoom: 4 }));
        L.marker([latitude, longitude], { icon: this.customDefault }).addTo(map);

Ref: http://leafletjs.com/examples/custom-icons/

Until there is a good fix for this problem, may I suggest to add a runtime warning when _getIconUrl() (or whatever) unexpectedly encounters a data UI, pointing to a Github Issue URL via console.warn or something like that.

That would bring people with the same problem to the right place and suggest workarounds (like this one that worked for me).

That's the way React (dev builds) helps developers to identify problems.

From the react issue, a workaround by @PTihomir

import L from 'leaflet';
delete L.Icon.Default.prototype._getIconUrl;

L.Icon.Default.mergeOptions({
  iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
  iconUrl: require('leaflet/dist/images/marker-icon.png'),
  shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});

this fixes the issue without a change to the leaflet core files.

@codeofsumit: FYI, from what I understand, this workaround works for the three mentioned icons only.

The workaround will need do to be adjusted in case Leflet does (or will in future) require other icons in a similar way (perhaps for other components - I don't know).


For those unfamiliar with Webpack: Webpack will assign new URLs to these properties:

/***/ 5305024559067547:
/***/ function(module, exports, __webpack_require__) {

    module.exports = __webpack_require__.p + "d95d69fa8a7dfe391399e22c0c45e203.png";

/***/ },


...


    _leaflet2['default'].Icon.Default.mergeOptions({
      iconRetinaUrl: __webpack_require__(5305024559067547),
      iconUrl: __webpack_require__(6633266380715105),
      shadowUrl: __webpack_require__(880063406195787)
    });

(details heavily depend on the Webpack configuration used)

@jampy yes of course. Hence it's a workaround. Any modifications to the leaflet core however will get deleted with every update regardless. I'll use the mentioned workaround until there's a proper fix as it seems like the least painful.

Same issue here, the detectIconPath function returns http://localhost:8080/2273e3d8ad9264b7daa5bdbf8e6b47f8.png") for the path url("http://localhost:8080/2273e3d8ad9264b7daa5bdbf8e6b47f8.png")

it's far from ideal, but I use webpack and I use this workaround

I copied the images into an images folder at my project's root

then in my package.json I added a postbuild npm script (in the scripts section)
"postbuild:prod": "./Post-Build4Prod.sh"

which copies an images folder to the dist

#bin/bash
cp -r ./images ./dist/.

then I define a customDefault for icons

    this.customDefault = L.icon({
        iconUrl: 'images/marker-icon.png',
        shadowUrl: 'images/marker-shadow.png',
    });

and use that wherever

    L.marker([latitude, longitude], { icon: this.customDefault }).addTo(map);

@ajoslin103 , consider this workaround. It's simpler and you end up with the same result.

I used the following to workaround this in a Vue webpack project:

import L from 'leaflet';

L.Icon.Default.imagePath = '/';
L.Icon.Default.mergeOptions({
    iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
    iconUrl: require('leaflet/dist/images/marker-icon.png'),
    shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});

I've had the same issue with Django's collectstatic and CachedStaticFilesStorage which adds a hash of the file content to the name of static files, so marker-icon.png becomes marker-icon.2273e3d8ad92.png and then the regexp at the end of _detectIconPath fails to match.

I changed it to replace(/marker-icon[^"']+\.png[\"\']?\)$/, '') which worked for me.

I have this problem currently, as well.
Using Leaflet 1.0.3, and Angular2.
I confess, I don't see how to resolve it given this thread.

For Angular 2 & 4
I create a file require.d.ts with the code :

interface WebpackRequire {
    <T>(path: string): T;
    (paths: string[], callback: (...modules: any[]) => void): void;
    ensure: (paths: string[], callback: (require: <T>(path: string) => T) => void) => void;
}
interface NodeRequire extends WebpackRequire {}
declare var require: NodeRequire;

and then using require for this problem:

                  this.marker = L.marker(e.latlng, {
                    icon: L.icon({
                        iconUrl: require <any>('../../images/marker-icon.png'),
                        shadowUrl: require <any>('../../images/marker-shadow.png'),
                    })

Workaround (with size & anchor) for Vue.js with webpack:

import L from 'leaflet'

// BUG https://github.com/Leaflet/Leaflet/issues/4968
import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png'
import iconUrl from 'leaflet/dist/images/marker-icon.png'
import shadowUrl from 'leaflet/dist/images/marker-shadow.png'
L.Marker.prototype.options.icon = L.icon({
  iconRetinaUrl,
  iconUrl,
  shadowUrl,
  iconSize: [25, 41],
  iconAnchor: [12, 41],
  popupAnchor: [1, -34],
  tooltipAnchor: [16, -28],
  shadowSize: [41, 41]
})

I made some quick performance tests for inlining the default icon as base64 versus using an external image URL. When loading _a lot_ of markers (1000, in my case), the performance is noticably worse for base64 inlined images.

Here's the Chrome devtools performance view when loading 1000 marker icons as external URLs:

1000 markers icons as external URLs

In comparison, with 1000 marker icons as inlined base64 (please notice different scale for timeline, sorry):

1000 markers icons as inlined base64 URLs

As can be seen, for some reason the layer composition is delayed when using inlined images, which makes the whole load take roughly a second longer.

For casual use, this probably doesn't matter, but if you use a lot of markers, this might be relevant.

A proposal for how to handle this long term:

  • Fix #5041 to address the issues mentioned in the code review; this would make inlined icons work out of the box
  • Log a warning if inlined icons are used, to indicate that this might not be ideal

Another option would be to revert to the old (0.7) method for detecting the image path, but we know that had other issues that we couldn't resolve.

Hi @perliedman,

Nice profiling!
It answers the doubts I had regarding down side of re-using inline images.

Indeed PR #5041 could be refactored so that Leaflet could work right away when icon images are inlined in the CSS (by a build engine like webpack).
The solution I can think of (mostly similar to the mentioned PR) would imply creating 1 class per image, hence it would increase the CSS (and probably JS) file size by a few dozens of bytes.

Example: http://playground-leaflet.rhcloud.com/dulox/1/edit?html,css,output

But like I mentioned previously, it is unfortunate that such a change for compatibility reason (with build engines) affects users that do not use these frameworks and build processes.

On the other hand, the result could be "cleaner" since we could get rid of hard-coded file names in IconDefault class options, and delegate the full path (including file name) to the CSS.
This is very interesting because (if I understand correctly) the whole point of this complex detection is to de-couple images location from the JS file, and rely on their relative location to the CSS file instead. Therefore it makes sense to me that even the file name is defined in the CSS.

But then trying to maintain compatibility with imagePath option could also be complicated, since it would probably require reworking the image paths that were previously detected, to replace the leading path and keep the file name. Therefore we would introduce a new RegExp there.

Finally, I am not sure this would cover all the cases.
Build processes can be highly customized, leading to very different situations regarding static assets like icon images. There may still be some corner cases where even the above would fail.

@ghybs I like that example ( http://playground-leaflet.rhcloud.com/dulox/)!

Maybe overdoing things, but what do you think about also specifying icon _size_ in this CSS (using width and height)? I can imagine someone overriding those CSS rules to change the default icon, only to find it now has the wrong dimensions.

With or without the dimensions in CSS, I think this is a nice way forward. Would you care to make a PR along the lines of your example? If you don't have the time or energy right now, I can go ahead and do it, just tell me what you prefer.

I'm eager to close this one, since I actually happened to stumble upon this issue myself.

Hi @perliedman,

I agree it would be even better to be able to specify icon size as well through CSS.

But in that case, for consistency we should also be able to specify the anchor point. I am not sure which CSS rule would be suitable for this (maybe margin?). If that is possible, then the result would be very nice I think: the icons would be entirely defined in CSS.

Pushing even further, that could be an extra way of defining an icon: specify 3 CSS classes (icon, retina, shadow) and Leaflet would extract all the icon options from them.

Please feel free to work on this, I am not sure when I will be able to get some time unfortunately...

Building on the previous example, here is a concept of reading padding and margin CSS rules to determine iconAnchor (shadowAnchor) and popupAnchor options:
http://playground-leaflet.rhcloud.com/xuvi/1/edit?html,css,output

I do not like the fact that I used padding to determine the iconAnchor, because in the end Leaflet uses margin to position the icon image…
But that it the easiest I found to quickly specify things in CSS, and avoid reverting values compared to how we specify icon options.

Although I like the result of specifying everything in CSS, there is still more work needed to maintain backwards compatibility with L.Icon.Default.imagePath.

I apologize I do not have time to create a PR.

I just diagnosed another case where _detectIconPath fails: When using Firefox (tested with ESR and current versions) and setting Preferences → Content → Colors… → Override the colors specified by the page with your selections above: Always   Firefox will remove background-image properties including the one in .leaflet-default-icon-path and thus break _detectIconPath.

I'm not sure how many other people use this Firefox feature, but I have been using it for a very long time.

Can url()be used as value of other CSS-properties as well? Like using the following CSS: .leaflet-default-icon-path { -leaflet-icon: url(images/marker-icon.png); } or is it impossible to define and use custom CSS-properties oneself? No browser would need to _understand_ the -leaflet-icon property, they would however still need to populate it and make its value available to scripts.

Hi @roques,

Thank you for reporting this issue when using this specific option in Firefox!
Looks like Chrome has a High Contrast extension but which only changes the colours, without breaking anything in Leaflet.

Unfortunately Firefox does strip CSS properties it does not understand.
However the image CSS data type can be used with a few other rules, including cursor, which looks to work fine even when the colours override setting is used in Firefox:
http://playground-leaflet.rhcloud.com/yov/1/edit?html,css,output

I find it less elegant to use cursor instead of background-image, since its usage is much further from what we would do to make a marker through CSS styling… but anyway this is already a hack for path detection only.

I was about to make a PR to address the webpack issue, I will include this workaround right now.
I have no clue how we could make an automated test for this case, but I think the workaround should already be good.

Any one has found a clear solution for this issue ?

Building on @Shiva127 's answer, for anyone using Angular + Angular CLI:

You can put this in app.module.ts:

// BUG https://github.com/Leaflet/Leaflet/issues/4968
import {icon, Marker} from 'leaflet';
const iconRetinaUrl = 'assets/marker-icon-2x.png';
const iconUrl = 'assets/marker-icon.png';
const shadowUrl = 'assets/marker-shadow.png';
const iconDefault = icon({
  iconRetinaUrl,
  iconUrl,
  shadowUrl,
  iconSize: [25, 41],
  iconAnchor: [12, 41],
  popupAnchor: [1, -34],
  tooltipAnchor: [16, -28],
  shadowSize: [41, 41]
});
Marker.prototype.options.icon = iconDefault;

and add the glob line in your .angular-cli.json:

"assets": [
        "assets",
        "favicon.ico",
        { "glob": "**/*", "input": "../node_modules/leaflet/dist/images/", "output": "./assets/" }
      ],

This will copy the icons into the assets folder in the dist folder at build time (you wont see them in src/assets). Also, putting the work around in app.module.ts is a good place to keep global prototype modifying imports (such as RxJS observable patches). With that, importing Marker else where in the codebase will function correctly.

I have fixed this issue like this.

I have provided a default icon to marker drawOptions:

const myIcon = L.icon({
    ...
    ...
});

const drawOptions = {
      ....
      marker: {
         icon: myIcon
      }
};

...

Then saved path to icon on L.Draw.Event.CREATED

this.map.on(L.Draw.Event.CREATED, (e) => {

      const layer: any = (e as L.DrawEvents.Created).layer;
      const type = (e as L.DrawEvents.Created).layerType;

      // Create a marker.
      if (type === 'marker') {

        let feature = layer.feature = layer.feature || {};
        feature.type = "Feature";
        feature.properties = feature.properties || {};
        feature.properties["markerIconsPath"] = "/assets/icons/";
       }
 });


Finally on displaying the layers i set imagePath:

if(layer instanceof L.Marker) {
            L.Icon.Default.imagePath = layer.feature.properties.markerIconsPath;
            layer.setIcon(greenIcon);
 }

@codeofsumit are these fixes included in latest version 1.3.1? Surprisingly, I am using version 1.3.1 and still facing same issue.

@vishalrajole to my knowledge, the only PR submitted to address this is #5041, which has been open with requested changes for a _long_ time.

We also have the alternative solution #5771, which is nice but involved more changes.

So, to sum up: no one has submitted an accepted PR to address this, help is welcome!

@perliedman from the looks of it the involved changed of #5771 are necessary. Otherwise you'll keep having this issue in different circumstances. Why not just merge that?

Hi @mb21,

the involved changed of #5771 are necessary

Actually the proposed changes are of 2 types:

  • reading each image path instead of just the images folder location, so that urls modified by build engines (like webpack file loader) are read as is instead of reconstructed / guessed.
  • reading all other default icon options from CSS, so that all the configuration is gathered in a single place.

The 2nd point is interesting if the 1st one cannot be avoided, even if it adds quite some code.

The 1st one is actually a favour to developers using a build engine that fiddles with urls in CSS. It keeps Leaflet _zero config spirit_ even in a new environment where the developer spends quite some time tuning their configuration (if you use webpack file loader, you need a custom config anyway), at the expense of adding quite some code for everyone else, which is IMHO _against_ Leaflet spirit (support common usage in core, delegate other use cases to plugins).
You can very easily solve the issue in the first place by specifying the image paths, typically using require(image) as shown in numerous above comments.

Therefore even though I authored that PR, I personnally feel uncomfortable merging it in core. The changes _are not necessary for the majority_.

They sure are nice to make developers' life easier, but the issue is primarily caused by interaction of build engines / framework wrappers, each having their specifities, and each having an easy way to tell Leaflet where the images are now, using the already existing API.

Maybe we should rather address this issue with better documentation (e.g. a section dedicated to usage with build engines / frameworks?), and/or a plugin?

Well, I'm not sure I understand the intricacies. But it would sure be nice for all sorts of developers if _all_ image paths could be specified in CSS. It's not only webpack-users, it's also people like me using the Rails or Django asset pipeline that appends a hash to each static asset...

I've experienced exactly the same problem:
data:image/png;base64,iVBORw0...AAAAASUVORK5CYII=")marker-shadow.png net::ERR_INVALID_URL

Solution is to replace:

getIconUrl: function (name) {
        if (!IconDefault.imagePath) {   // Deprecated, backwards-compatibility only
            IconDefault.imagePath = this._detectIconPath();
        }

        // @option imagePath: String
        // `Icon.Default` will try to auto-detect the location of the
        // blue icon images. If you are placing these images in a non-standard
        // way, set this option to point to the right path.
        return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
    },

with:

 // ...
  const url = (this.options.imagePath || L.Icon.Default.imagePath);

  return url.slice(0, -2);

as suggested by codeofsumit.

I found it really annoying and that fact that there is a PR to fix it but not merged because of the "feeling" that "The changes are not necessary for the majority." Sorry, but I've seen people struggling with it in PHP, RoR, Python (Django) and node.js, so where do you think is "the majority" beyond these groups? What compatible framework would you recommend?

I agree with @macwis

Having the same issue and this thread is very long. Why not merge the PR ?

That is not a feeling, it is a fact: the majority uses no framework, or those that do not fiddle with CSS.
Last time I checked, Leaflet was no 1 downloaded from unpkg CDN, i.e. unbundled and unfiddled CSS.

The proper solution is to define the default icon options, as explained in many above messages.
Many frameworks do so as part of their Leaflet integration plugin.

If you want a more automatic solution, you still have the option to publish a plugin.

Why not merge the PR ?

Read the comments, e.g. https://github.com/Leaflet/Leaflet/issues/4968#issuecomment-382639119

I’d just like to throw in a little hint: you can use CDN also when using a framework. That’s what we do for example with our React App. We load big libs via CDN.

@googol7 thank you for your input.

Please correct me if I am wrong: if you load Leaflet through the CDN, it very probably means you do not alter its CSS. Hence your users are in the majority.

@ghybs: What I had to do was the following:

// Workaround: https://github.com/Leaflet/Leaflet/issues/4968#issuecomment-269750768
/* eslint-disable no-underscore-dangle, global-require */
delete L.Icon.Default.prototype._getIconUrl

L.Icon.Default.mergeOptions({
    iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
    iconUrl: require("leaflet/dist/images/marker-icon.png"),
    shadowUrl: require("leaflet/dist/images/marker-shadow.png"),
})
/* eslint-enable no-underscore-dangle, global-require */

our webpack config looks like this:

module.exports = {
    externals: {
        leaflet: "L",
    },
}

@googol7 thank you for the details of your config.

So you mean you load Leaflet JS from the CDN, but bundle the CSS and images in your app.

@ghybs yes I guess that’s what’s happening here.

Be sure to follow the first two steps: https://leafletjs.com/examples/quick-start/
I had a similar issue because I was using the css of someone else's tutorial, but this one must be used

<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin=""/>

and then the Leaflet script immediately after that

<script src="https://unpkg.com/[email protected]/dist/leaflet.js" integrity="sha512-/Nsx9X4HebavoBvEBuyp3I7od5tA0UzAxs+j83KgC8PU0kgB4XiK4Lfe4y4cgBtaRJQEIFCW+oC506aPT2L1zw==" crossorigin=""></script>

Hi all,

I published Leaflet plugin "leaflet-defaulticon-compatibility" that takes code from my PR https://github.com/Leaflet/Leaflet/pull/5771.
By using that plugin, the Leaflet Default Icon no longer tries to re-build the icon image paths, but fully relies on the CSS. That way, it becomes fully compatible with build engines and frameworks that automatically manage assets based on CSS (and usually re-write url()'s).

Simply load the plugin (CSS + JS) _after_ Leaflet.
E.g. in webpack:

import 'leaflet/dist/leaflet.css'
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css'
import * as L from 'leaflet'
import 'leaflet-defaulticon-compatibility'

(demo)

While I can understand that many developers would have preferred such feature to be incorporated directly in Leaflet core, it has been argued that the added code is useless to a majority of end-users. Therefore, aligning with Leaflet spirit of keeping its core simple, I decided to make it a plugin.

Please feel free to open an issue on the plugin repo should you have troubles or concerns with how it works.

The fact is, that if you are using webpack you get this issue. I see the trend of more and more sites using webpack. Placing this as a plugin is less than ideal, IMHO, as I don't see people looking for a plugin to fix this kind of issue (much like I did when I opened a dup).
I would very much like to see this inside leaflet as this is more of a bug fix than a feature...

If you want to resolve this issue in Angular 6, just write in angular.json:

 {
         "glob": "**/*",
         "input": "./node_modules/leaflet/dist/images/",
         "output": "./assets/leaflet/"
  }

After that, override Marker default behavior, as some of previous answers suggests:

import { Icon, icon, Marker, marker } from 'leaflet';

@Component({
   selector: 'app-something',
   templateUrl: './events.component.html',
   styleUrls: ['./events.component.scss']
})
export class SomeComponent implements OnInit {
  // Override default Icons
  private defaultIcon: Icon = icon({
    iconUrl: 'assets/leaflet/marker-icon.png',
    shadowUrl: 'assets/leaflet/marker-shadow.png'
  });

  ngOnInit() {
     Marker.prototype.options.icon = this.defaultIcon;
  }
}

The Angular package I used and had the same issue as here is: ngx-leaflet

NOTE:
There is small catch in Angular 6, as answer from _t.animal_ on StackOverflow says

Be aware that in Angular 6 there are two relevant builders in the architects build and test.

Make sure you put it under build.

@marko-jovanovic thanks for the info, but what if I'm not using these assets and would like to reduce my package size?
Your suggestion still falls under my definition of a workaround, IMO.

@HarelM Well I could not come with any other solution because I was in a hurry to finish the school project. It's not perfect and I know it increases the package size, but my solution was enough for me, and hoped my answer could help others too somehow.

@marko-jovanovic your solution is great and I too hope it can help others. I'm just hoping for a solution, not a workaround :-)

@marko-jovanovic Hi, I am too sitting on a school project (Angular 6) and can't figure out why stuff does not work for me. To be honest I am a total noob to all of this stuff here.

When I insert your code in the ngOnInit-Function of my component, it throws an error at the part where you set iconUrl and shadowUrl:

Argument of type '{ iconUrl: (options: IconOptions) => Icon<IconOptions>; shadowUrl: any; }' is not assignable to parameter of type 'IconOptions'. Types of property 'iconUrl' are incompatible. Type '(options: IconOptions) => Icon<IconOptions>' is not assignable to type 'string'.

Am I missing something? Thanks in advance!

@gittiker I've updated an answer with the imports, component and ngOnInit example. Let me know if everything went fine. :)

@gittiker I've updated an answer with the imports, component and ngOnInit example. Let me know if everything went fine. :)

Yes thank you very much, it finally works. I had to manipulate your URL a little bit so it is
'assets/leaflet/images/marker-icon.png instead of 'assets/leaflet/marker-icon.png',. Same goes for the shadow image.

@crob611 Thank you very much, I tried to solve the problem with this method.

@marko-jovanovic you saved me! but how @HarelM says, isn't there a solution?

thank you very much, but for me it served the following code: (Angular 6 and leaflet1.3.4)
First step
(https://codehandbook.org/use-leaflet-in-angular/)
But then the icon was not shown
error get icon image
net::ERR_INVALID_URL
solve it by inserting the following code in the component
delete L.Icon.Default.prototype._getIconUrl;`

L.Icon.Default.mergeOptions({
  iconRetinaUrl: '/assets/leaflet/dist/images/marker-icon-2x.png',
  iconUrl: '/assets/leaflet/dist/images/marker-icon.png',
  shadowUrl: '/assets/leaflet/dist/images/marker-shadow.png',
});`

Solution using leaflet 1.3.4 with vue2-leaflet 1.2.3:

import { L, LControlAttribution, LMap, LTileLayer, LMarker, LGeoJson, LPopup } from 'vue2-leaflet'
delete L.Icon.Default.prototype._getIconUrl
L.Icon.Default.mergeOptions({
  iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
  iconUrl: require('leaflet/dist/images/marker-icon.png'),
  shadowUrl: require('leaflet/dist/images/marker-shadow.png')
})

My 2 cents: my problem with webpack was just due to the hashed filenames, so I've configured file-loader in order to not hash leaflet's images:

use: [{
    loader: 'file-loader',
    options:
      {
        name(file) {
          console.log(file)
          if (file.match(/(layers-2x\.png|layers\.png|marker-icon\.png|marker-icon-2x\.png|marker-shadow\.png)/)) {
            return '[name].[ext]'
          }

          return '[name]-[hash].[ext]'
        },
        context: 'app/frontend' // <-- project specific
      }
  }]

Rude but efficient AFAIK.

@ghybs thanks for the hotfix. I've ran into this bug a few times in different projects. This entire thread seems absurd that its not fixed or isn't seen as a problem.

google brought me here because using the library with Webpack was giving me this error.

Does anyone know why aren't those images inlined as svg?

I think this could easily solved with postcss and postcss-inline-svg. The icons would become inline svg files instead of external png. The icons would be crispier as this problem goes away.

Does anyone know why aren't those images inlined as svg?

Legacy browser support.

Thanks @IvanSanchez

Then I see two potential solutions. One is to inline the images as base64 encoded .png. The other alternative is to inline .svg icons and make devs that target legacy platforms to override the default icons.

Is any of these solutions worth a pull request?

Out of all the browsers supported by leaflet, the following don't have support for svg (caniuse).

  • IE 7 and 8
  • Android browser 2.*

inline the images as base64

See https://github.com/Leaflet/Leaflet/issues/4968#issuecomment-322422045

Had to add anchor and size too to make it work, e.g.

   import icon from 'leaflet/dist/images/marker-icon.png';
   import iconShadow from 'leaflet/dist/images/marker-shadow.png';

   let DefaultIcon = L.icon({
      iconUrl: icon,
      shadowUrl: iconShadow,
      iconSize: [24,36],
      iconAnchor: [12,36]
    });

    L.Marker.prototype.options.icon = DefaultIcon; 

I also have the same issue (webpack using Flask, so all the elements are supposed to be in a static folder), but @giorgi-m fix is not enough, as I get a '"exports" is read-only' error (Firefox, seems to be linked to the png imports?).
I see that the issue is closed, but we still see issues with 1.4.0, so I wonder what the fix is?

Seeing this issue with vue2-leaflet 2.0.2 and leaflet 1.4.0.

this appears to have existed for quite awhile, and half of the presented solutions don't seem to work.

Has anyone figured out the root of this problem?

i am having the same problem with versions "vue2-leaflet": "2.0.3" leaflet "leaflet": "1.4.0".

also running webpack.

We are successfully using vue2-leaflet 2.0.3 and leaflet 1.4.0 using a solution found in this same issue:

import L from 'leaflet'
require('../../node_modules/leaflet/dist/leaflet.css')

// FIX leaflet's default icon path problems with webpack
delete L.Icon.Default.prototype._getIconUrl
L.Icon.Default.mergeOptions({
  iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
  iconUrl: require('leaflet/dist/images/marker-icon.png'),
  shadowUrl: require('leaflet/dist/images/marker-shadow.png')
})

I guess the better question that needs to be asked is why this wasn't fixed with the merged code which caused the issue to be closed? Since a workaround that works is great, but this should work out of the box and still needs to be an open issue.

Dear all,

Leaflet works out-of-the-box.

Webpack (and other build engines) combined with Leaflet does not work out-of-the-box.

Please refer to solution proposed in https://github.com/Leaflet/Leaflet/issues/4968#issuecomment-399857656: leaflet-defaulticon-compatibility

While I can understand that many developers would have preferred such feature to be incorporated directly in Leaflet core, it has been argued that the added code is useless to a majority of end-users. Therefore, aligning with Leaflet spirit of keeping its core simple, I decided to make it a plugin.

With webpack, another typical solution, once you have configured your loaders:

import L from 'leaflet';
delete L.Icon.Default.prototype._getIconUrl;

L.Icon.Default.mergeOptions({
  iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
  iconUrl: require('leaflet/dist/images/marker-icon.png'),
  shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});

There are other solutions proposed higher in this thread for other build engines and framework combinations.

To prevent these solutions from being further buried under comments, I am going to lock this thread.

Thank you all for the shared solutions! :+1:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jblarsen picture jblarsen  ·  3Comments

pgeyman picture pgeyman  ·  3Comments

ssured picture ssured  ·  3Comments

gdbd picture gdbd  ·  3Comments

onethread picture onethread  ·  3Comments