Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
* Portions of this code and logic copied from OpenLayers and
* redistributed under the original Clear BSD license terms:
* Copyright 2005-2010 OpenLayers Contributors, released under
* the Clear BSD license. See authors.txt for a list of contributors.
* All rights reserved.
* --
* Leaflet-specific modifications are released under the following
* terms:
* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* for more details.
L.TileLayer.Bing = L.TileLayer.extend({
supportedTypes: ['Road', 'Aerial', 'AerialWithLabels'],
attributionTemplate: '<span style="display:inline-block">' +
'<a target="_blank" href="">' +
'<img src="{logo}" /></a><br><span>{copyrights}' +
'<a style="white-space: nowrap" target="_blank" '+
'href="">' +
'Terms of Use</a></span></span>',
initialize: function(/*String*/ apiKey, /*String*/ mapType, /*Object*/ options) {
this._apiKey = apiKey;
this._mapType = mapType;
L.Util.setOptions(this, options);
_loadMetadata: function() {
this._callbackId = "_l_tilelayer_bing_" + (L.TileLayer.Bing._callbackId++);
var that = this;
window[this._callbackId] = function() {
L.TileLayer.Bing.processMetadata.apply(that, arguments);
var params = {
key: this._apiKey,
jsonp: this._callbackId,
include: 'ImageryProviders'
url = "" +
this._mapType + L.Util.getParamString(params),
script = document.createElement("script");
script.type = "text/javascript";
script.src = url; = this._callbackId;
_onMetadataLoaded: function() {},
onAdd: function(map, insertAtTheBottom) {
if (!this.metadata) {
this._onMetadataLoaded = L.Util.bind(function() {, map, insertAtTheBottom);
map.on('moveend', this._updateAttribution, this);
}, this);
} else {, map, insertAtTheBottom);
map.on('moveend', this._updateAttribution, this);
onRemove: function(map) {
if (this._map.attributionControl) {
}'moveend', this._updateAttribution, this);, map);
getTileUrl: function(xy, z) {
var subdomains = this.options.subdomains,
quadDigits = [],
i = z,
// borrowed directly from OpenLayers
for (; i > 0; --i) {
digit = '0';
mask = 1 << (i - 1);
if ((xy.x & mask) != 0) {
if ((xy.y & mask) != 0) {
return this._url
.replace('{subdomain}', subdomains[(xy.x + xy.y) % subdomains.length])
.replace('{quadkey}', quadDigits.join(""));
_updateAttribution: function() {
if (this._map.attributionControl) {
var metadata = this.metadata;
var res = metadata.resourceSets[0].resources[0];
var bounds = this._map.getBounds();
var providers = res.imageryProviders, zoom = this._map.getZoom() + 1,
copyrights = "", provider, i, ii, j, jj, bbox, coverage;
for (i=0,ii=providers.length; i<ii; ++i) {
provider = providers[i];
for (j=0,jj=provider.coverageAreas.length; j<jj; ++j) {
coverage = provider.coverageAreas[j];
if (zoom <= coverage.zoomMax && zoom >= coverage.zoomMin && coverage.bbox.intersects(bounds)) {
copyrights += provider.attribution + " ";
j = jj;
this._map.attributionControl._attributions = {};
this.attribution = this.attributionTemplate
.replace('{logo}', metadata.brandLogoUri)
.replace('{copyrights}', copyrights);
L.TileLayer.Bing._callbackId = 0;
L.TileLayer.Bing.processMetadata = function(metadata) {
if (metadata.authenticationResultCode != 'ValidCredentials') {
throw "Invalid Bing Maps API Key"
if (!metadata.resourceSets.length || !metadata.resourceSets[0].resources.length) {
throw "No resources returned, perhaps " + this._mapType + " is an invalid map type?";
if (metadata.statusCode != 200) {
throw "Bing Maps API request failed with status code " + metadata.statusCode;
this.metadata = metadata;
var res = metadata.resourceSets[0].resources[0],
providers = res.imageryProviders,
i = 0,
script = document.getElementById(this._callbackId);
for (; i<providers.length; i++) {
provider = providers[i];
for (j=0; j<provider.coverageAreas.length; j++) {
bbox = provider.coverageAreas[j].bbox;
provider.coverageAreas[j].bbox = new L.LatLngBounds(new L.LatLng(bbox[0],bbox[1],true),new L.LatLng(bbox[2],bbox[3], true));
this._url = res.imageUrl.replace('{culture}','en-US');
this.options.subdomains = [].concat(res.imageUrlSubdomains);
window[this._callbackId] = undefined; // cannot delete from window in IE
delete this._callbackId;

Hello! I'm trying to get this to work but Bing is giving me '400 Bad Request' responses. Is there someplace with a working example of this I could look at? Thanks!


pagameba commented Mar 9, 2012

Hi, sorry I don't have a working example any more, I deleted it somewhere along the way. I will be coming back to this experiment in a few weeks as I will have need of it again, until then I don't have time to debug problems. I expect the issue is that Bing has changed something in their API and this code would need to be updated to accommodate the changes. This code was adapted from OpenLayers Bing support, I notice the OpenLayers Bing example is still working so perhaps you could start there if you need to get it working sooner ..



rclark commented Mar 11, 2012

I had some trouble with the processMetadata function. It looks to me like Leaflet commit e5f934e changed L.Utils.bind in such a way as to break your callback. A clumsy adjustment to your _loadMetadata routine fixed it for me:

//window[this._callbackId] = L.Util.bind(L.TileLayer.Bing.processMetadata, this);
that = this;
window[this._callbackId] = function() { L.TileLayer.Bing.processMetadata.apply(that, arguments); };

I forked your Gist with my changes



pagameba commented Mar 11, 2012

/me pokes


pagameba commented Mar 14, 2012

:) sorry, its been a busy week. Does Ryan's fork linked above work for you? I haven't tried it yet.

Yes, Ryan's changes worked. Thanks! :D

It'll also be nice if you could explicitly state what license this code is under...


pagameba commented Mar 14, 2012

Great point. I've updated this Gist with Ryan's changes (untested) and appropriate license and copyright details.



rclark commented Mar 14, 2012

There are still problems, I think, if you want to have more than one Bing layer in your app.

Issue with multiple Bing Layers and attributions being added multiple times:


`````` this._map.attributionControl.removeAttribution(this.attribution);```


    this._map.attributionControl._attributions = {};

Forked here:

pagameba commented Apr 23, 2012

updated gist with ccgdesigns contribution.

When I try to overlay a custom tilelayer over the bing layer, the bing one always supersedes. As in, I want the custom layer to be on top. The custom layer falls under the aerial imagery, so i don't see it at all!


pagameba commented Jun 18, 2012

This code has been incorporated into, I suggest following up there.

We are using leaflet with bing map
I am wondering if it is possible to retrieve session id to decrease billable calls to bingmap
If we use session id instead of key after first call they are nonbillable.
For bingmap ajax there is getcredentials function but I could not find anything similar with leaflet.
callback function only includes traceid.

marie15 commented May 31, 2016

Hi everybody, I'm trying to add tile bing to my map, espacially 'AerialWithLabels' type. So do you have any idea how to use that? I'm using leaflet with angularJS. Honestly, I'm beginner in leaflet.

Any help is appreciated. Thankx for advance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment