├── CHANGELOG.md
├── README.md
├── index.html
├── index.js
├── leaflet-bing-layer.js
├── leaflet-bing-layer.min.js
└── package.json
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | This project adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | ## [3.3.0] - 2018-03-14
7 |
8 | - ADDED: `options.style` to use [custom map styles](https://msdn.microsoft.com/en-us/library/mt823632.aspx)
9 | - ADDED: `AerialWithLabelsOnDemand` imagery set
10 | - ADDED: `minNativeZoom` and `maxNativeZoom` options
11 | - FIXED: imageryProviders error [#25](https://github.com/digidem/leaflet-bing-layer/pull/25)
12 |
13 | ## [v3.2.0] - 2017-08-09
14 |
15 | - CHANGED: Use `https` requests
16 | - ADDED: Additional imagery sets (`RoadOnDemand`, `CanvasLight`, `CanvasDark`, `CanvasGray`, `OrdnanceSurvey`)
17 |
18 | ## [v3.1.0] - 2016-04-29
19 |
20 | - ADDED: Use `https` for Bing API requests.
21 |
22 | ## [v3.0.1] - 2015-12-13
23 |
24 | - FIXED: options.BingMapsKey backwards compatability
25 | - FIXED: options.bingMapsKey was not working for getMetaData
26 | - FIXED: catch errors (and log to console) for jsonp
27 |
28 | ## [v3.0.0] - 2015-12-08
29 |
30 | - FIXED: **[BREAKING]** Export factory function on `L.tileLayer.bing` not `L.TileLayer.bing`
31 | - CHANGED: BingMapsKey is now passed on `options.bingMapsKey` (`options.BingMapsKey` will still work, but for convention this should start with a lowercase character)
32 | - IMPROVED: Package with browserify and require dependencies
33 | - IMPROVED: Throws error if invalid imagerySet is passed as option
34 | - ADDED: `getMetaData` method
35 |
36 | ## v2.0.2 - 2015-12-03
37 |
38 | Initial release
39 |
40 | [Unreleased]: https://github.com/digidem/leaflet-bing-layer/compare/v3.2.0...HEAD
41 | [v3.2.0]: https://github.com/digidem/leaflet-bing-layer/compare/v3.1.0...v3.2.0
42 | [v3.1.0]: https://github.com/digidem/leaflet-bing-layer/compare/v3.0.1...v3.1.0
43 | [v3.0.1]: https://github.com/digidem/leaflet-bing-layer/compare/v3.0.0...v3.0.1
44 | [v3.0.0]: https://github.com/digidem/leaflet-bing-layer/compare/v2.0.2...v3.0.0
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # leaflet-bing-layer
2 |
3 | Bing Maps Layer for Leaflet v1.0.0
4 |
5 |
6 | ### L.TileLayer.Bing(options|BingMapsKey)
7 |
8 | Create a new Bing Maps Layer. Depends on [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which needs a polyfill for [older browsers](http://caniuse.com/#feat=promises).
9 |
10 | ### Parameters
11 |
12 | | parameter | type | description |
13 | | ----------------------------- | -------------- | ----------------------------------------------------------------------------------------------------- |
14 | | `options` | string\|object | A valid [Bing Maps Key](https://msdn.microsoft.com/en-us/library/ff428642.aspx) or an `options` object. `options` inherits from [L.TileLayer options](http://mourner.github.io/Leaflet/reference.html#tilelayer-options) (e.g. you can use `minZoom` and `opacity` and etc) |
15 | | `options.bingMapsKey` | string | A valid Bing Maps Key [_required_] |
16 | | `[options.imagerySet]` | string | _optional:_ [Imagery Type](https://msdn.microsoft.com/en-us/library/ff701716.aspx) [_default=Aerial_]
- `Aerial` - Aerial imagery
- `AerialWithLabels` - Aerial imagery with a road overlay
- `AerialWithLabelsOnDemand` - Aerial imagery with on-demand road overlay.
- `CanvasDark` - A dark version of the road maps.
- `CanvasLight` - A lighter version of the road maps which also has some of the details such as hill shading disabled.
- `CanvasGray` - A grayscale version of the road maps.
- `Road` - Roads without additional imagery. Uses the legacy static tile service.
- `RoadOnDemand` - Roads without additional imagery. Uses the dynamic tile service.
- `OrdnanceSurvey` - Ordnance Survey imagery. This imagery is visible only for the London area.
**[Not supported](https://social.msdn.microsoft.com/Forums/en-US/3d80d4a6-f4c9-4926-a336-e0d545b1ef3c/is-it-possible-to-retrieve-birdseye-map-tiles-using-rest-services?forum=bingmapsservices)**: `Birdseye` and `BirdseyeWithLabels` |
17 | | `[options.culture]` | string | _optional:_ Language for labels, [see options](https://msdn.microsoft.com/en-us/library/hh441729.aspx) [_default=en_US_] |
18 | | `[options.style]` | string | _optional:_ Use a [custom map style](https://msdn.microsoft.com/en-us/library/mt823632.aspx) - only works with the `AerialWithLabelsOnDemand` and `RoadOnDemand` imagerySet options. |
19 |
20 | Other options are passed through to a [Leaflet TileLayer](http://leafletjs.com/reference-1.3.0.html#tilelayer-l-tilelayer)
21 |
22 | ### Methods
23 |
24 | | Method | Returns | Description |
25 | | ---------- | -------------- | ------------- |
26 | | `getMetaData( latlng, zoom)` | `Promise` | Get the [Bing Imagery metadata](https://msdn.microsoft.com/en-us/library/ff701712.aspx) for a specific [`LatLng`](http://leafletjs.com/reference.html#latlng) and zoom level. `latLng` or `zoom` are optional *if* the layer is attached to a map, they default to current map center and zoom. Returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves to the metadata JSON from Bing |
27 |
28 | ### Example
29 |
30 | ```js
31 | var map = L.map('map').setView([51.505, -0.09], 13)
32 | L.tileLayer.bing(MyBingMapsKey).addTo(map)
33 | ```
34 |
35 | [Live Example](http://digidem.github.io/leaflet-bing-layer/) see [index.html](index.html)
36 |
37 | ### License
38 |
39 | MIT
40 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Leaflet Bing Maps Layer
7 |
8 |
9 |
10 |
22 |
23 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var L = require('leaflet')
2 | var fetchJsonp = require('fetch-jsonp')
3 | var bboxIntersect = require('bbox-intersect')
4 |
5 | /**
6 | * Converts tile xyz coordinates to Quadkey
7 | * @param {Number} x
8 | * @param {Number} y
9 | * @param {Number} z
10 | * @return {Number} Quadkey
11 | */
12 | function toQuadKey (x, y, z) {
13 | var index = ''
14 | for (var i = z; i > 0; i--) {
15 | var b = 0
16 | var mask = 1 << (i - 1)
17 | if ((x & mask) !== 0) b++
18 | if ((y & mask) !== 0) b += 2
19 | index += b.toString()
20 | }
21 | return index
22 | }
23 |
24 | /**
25 | * Converts Leaflet BBoxString to Bing BBox
26 | * @param {String} bboxString 'southwest_lng,southwest_lat,northeast_lng,northeast_lat'
27 | * @return {Array} [south_lat, west_lng, north_lat, east_lng]
28 | */
29 | function toBingBBox (bboxString) {
30 | var bbox = bboxString.split(',')
31 | return [bbox[1], bbox[0], bbox[3], bbox[2]]
32 | }
33 |
34 | var VALID_IMAGERY_SETS = [
35 | 'Aerial',
36 | 'AerialWithLabels',
37 | 'AerialWithLabelsOnDemand',
38 | 'Road',
39 | 'RoadOnDemand',
40 | 'CanvasLight',
41 | 'CanvasDark',
42 | 'CanvasGray',
43 | 'OrdnanceSurvey'
44 | ]
45 |
46 | var DYNAMIC_IMAGERY_SETS = [
47 | 'AerialWithLabelsOnDemand',
48 | 'RoadOnDemand'
49 | ]
50 |
51 | /**
52 | * Create a new Bing Maps layer.
53 | * @param {string|object} options Either a [Bing Maps Key](https://msdn.microsoft.com/en-us/library/ff428642.aspx) or an options object
54 | * @param {string} options.BingMapsKey A valid Bing Maps Key (required)
55 | * @param {string} [options.imagerySet=Aerial] Type of imagery, see https://msdn.microsoft.com/en-us/library/ff701716.aspx
56 | * @param {string} [options.culture='en-US'] Language for labels, see https://msdn.microsoft.com/en-us/library/hh441729.aspx
57 | * @return {L.TileLayer} A Leaflet TileLayer to add to your map
58 | *
59 | * Create a basic map
60 | * @example
61 | * var map = L.map('map').setView([51.505, -0.09], 13)
62 | * L.TileLayer.Bing(MyBingMapsKey).addTo(map)
63 | */
64 | L.TileLayer.Bing = L.TileLayer.extend({
65 | options: {
66 | bingMapsKey: null, // Required
67 | imagerySet: 'Aerial',
68 | culture: 'en-US',
69 | minZoom: 1,
70 | minNativeZoom: 1,
71 | maxNativeZoom: 19
72 | },
73 |
74 | statics: {
75 | METADATA_URL: 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/{imagerySet}?key={bingMapsKey}&include=ImageryProviders&uriScheme=https&c={culture}',
76 | POINT_METADATA_URL: 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/{imagerySet}/{lat},{lng}?zl={z}&key={bingMapsKey}&uriScheme=https&c={culture}'
77 | },
78 |
79 | initialize: function (options) {
80 | if (typeof options === 'string') {
81 | options = { bingMapsKey: options }
82 | }
83 | if (options && options.BingMapsKey) {
84 | options.bingMapsKey = options.BingMapsKey
85 | console.warn('use options.bingMapsKey instead of options.BingMapsKey')
86 | }
87 | if (!options || !options.bingMapsKey) {
88 | throw new Error('Must supply options.BingMapsKey')
89 | }
90 | options = L.setOptions(this, options)
91 | if (VALID_IMAGERY_SETS.indexOf(options.imagerySet) < 0) {
92 | throw new Error("'" + options.imagerySet + "' is an invalid imagerySet, see https://github.com/digidem/leaflet-bing-layer#parameters")
93 | }
94 | if (options && options.style && DYNAMIC_IMAGERY_SETS.indexOf(options.imagerySet) < 0) {
95 | console.warn('Dynamic styles will only work with these imagerySet choices: ' + DYNAMIC_IMAGERY_SETS.join(', '))
96 | }
97 |
98 | var metaDataUrl = L.Util.template(L.TileLayer.Bing.METADATA_URL, {
99 | bingMapsKey: this.options.bingMapsKey,
100 | imagerySet: this.options.imagerySet,
101 | culture: this.options.culture
102 | })
103 |
104 | this._imageryProviders = []
105 | this._attributions = []
106 |
107 | // Keep a reference to the promise so we can use it later
108 | this._fetch = fetchJsonp(metaDataUrl, {jsonpCallback: 'jsonp'})
109 | .then(function (response) {
110 | return response.json()
111 | })
112 | .then(this._metaDataOnLoad.bind(this))
113 | .catch(console.error.bind(console))
114 |
115 | // for https://github.com/Leaflet/Leaflet/issues/137
116 | if (!L.Browser.android) {
117 | this.on('tileunload', this._onTileRemove)
118 | }
119 | },
120 |
121 | createTile: function (coords, done) {
122 | var tile = document.createElement('img')
123 |
124 | L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile))
125 | L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile))
126 |
127 | if (this.options.crossOrigin) {
128 | tile.crossOrigin = ''
129 | }
130 |
131 | /*
132 | Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
133 | http://www.w3.org/TR/WCAG20-TECHS/H67
134 | */
135 | tile.alt = ''
136 |
137 | // Don't create closure if we don't have to
138 | if (this._url) {
139 | tile.src = this.getTileUrl(coords)
140 | } else {
141 | this._fetch.then(function () {
142 | tile.src = this.getTileUrl(coords)
143 | }.bind(this)).catch(function (e) {
144 | console.error(e)
145 | done(e)
146 | })
147 | }
148 |
149 | return tile
150 | },
151 |
152 | getTileUrl: function (coords) {
153 | var quadkey = toQuadKey(coords.x, coords.y, coords.z)
154 | var url = L.Util.template(this._url, {
155 | quadkey: quadkey,
156 | subdomain: this._getSubdomain(coords),
157 | culture: this.options.culture
158 | })
159 | if (typeof this.options.style === 'string') {
160 | url += '&st=' + this.options.style
161 | }
162 | return url
163 | },
164 |
165 | // Update the attribution control every time the map is moved
166 | onAdd: function (map) {
167 | map.on('moveend', this._updateAttribution, this)
168 | L.TileLayer.prototype.onAdd.call(this, map)
169 | this._attributions.forEach(function (attribution) {
170 | map.attributionControl.addAttribution(attribution)
171 | })
172 | },
173 |
174 | // Clean up events and remove attributions from attribution control
175 | onRemove: function (map) {
176 | map.off('moveend', this._updateAttribution, this)
177 | this._attributions.forEach(function (attribution) {
178 | map.attributionControl.removeAttribution(attribution)
179 | })
180 | L.TileLayer.prototype.onRemove.call(this, map)
181 | },
182 |
183 | /**
184 | * Get the [Bing Imagery metadata](https://msdn.microsoft.com/en-us/library/ff701712.aspx)
185 | * for a specific [`LatLng`](http://leafletjs.com/reference.html#latlng)
186 | * and zoom level. If either `latlng` or `zoom` is omitted and the layer is attached
187 | * to a map, the map center and current map zoom are used.
188 | * @param {L.LatLng} latlng
189 | * @param {Number} zoom
190 | * @return {Promise} Resolves to the JSON metadata
191 | */
192 | getMetaData: function (latlng, zoom) {
193 | if (!this._map && (!latlng || !zoom)) {
194 | return Promise.reject(new Error('If layer is not attached to map, you must provide LatLng and zoom'))
195 | }
196 | latlng = latlng || this._map.getCenter()
197 | zoom = zoom || this._map.getZoom()
198 | var PointMetaDataUrl = L.Util.template(L.TileLayer.Bing.POINT_METADATA_URL, {
199 | bingMapsKey: this.options.bingMapsKey,
200 | imagerySet: this.options.imagerySet,
201 | z: zoom,
202 | lat: latlng.lat,
203 | lng: latlng.lng
204 | })
205 | return fetchJsonp(PointMetaDataUrl, {jsonpCallback: 'jsonp'})
206 | .then(function (response) {
207 | return response.json()
208 | })
209 | .catch(console.error.bind(console))
210 | },
211 |
212 | _metaDataOnLoad: function (metaData) {
213 | if (metaData.statusCode !== 200) {
214 | throw new Error('Bing Imagery Metadata error: \n' + JSON.stringify(metaData, null, ' '))
215 | }
216 | var resource = metaData.resourceSets[0].resources[0]
217 | this._url = resource.imageUrl
218 | this._imageryProviders = resource.imageryProviders || []
219 | this.options.subdomains = resource.imageUrlSubdomains
220 | this._updateAttribution()
221 | return Promise.resolve()
222 | },
223 |
224 | /**
225 | * Update the attribution control of the map with the provider attributions
226 | * within the current map bounds
227 | */
228 | _updateAttribution: function () {
229 | var map = this._map
230 | if (!map || !map.attributionControl) return
231 | var zoom = map.getZoom()
232 | var bbox = toBingBBox(map.getBounds().toBBoxString())
233 | this._fetch.then(function () {
234 | var newAttributions = this._getAttributions(bbox, zoom)
235 | var prevAttributions = this._attributions
236 | // Add any new provider attributions in the current area to the attribution control
237 | newAttributions.forEach(function (attr) {
238 | if (prevAttributions.indexOf(attr) > -1) return
239 | map.attributionControl.addAttribution(attr)
240 | })
241 | // Remove any attributions that are no longer in the current area from the attribution control
242 | prevAttributions.filter(function (attr) {
243 | if (newAttributions.indexOf(attr) > -1) return
244 | map.attributionControl.removeAttribution(attr)
245 | })
246 | this._attributions = newAttributions
247 | }.bind(this))
248 | },
249 |
250 | /**
251 | * Returns an array of attributions for given bbox and zoom
252 | * @private
253 | * @param {Array} bbox [west, south, east, north]
254 | * @param {Number} zoom
255 | * @return {Array} Array of attribution strings for each provider
256 | */
257 | _getAttributions: function (bbox, zoom) {
258 | return this._imageryProviders.reduce(function (attributions, provider) {
259 | for (var i = 0; i < provider.coverageAreas.length; i++) {
260 | if (bboxIntersect(bbox, provider.coverageAreas[i].bbox) &&
261 | zoom >= provider.coverageAreas[i].zoomMin &&
262 | zoom <= provider.coverageAreas[i].zoomMax) {
263 | attributions.push(provider.attribution)
264 | return attributions
265 | }
266 | }
267 | return attributions
268 | }, [])
269 | }
270 | })
271 |
272 | L.tileLayer.bing = function (options) {
273 | return new L.TileLayer.Bing(options)
274 | }
275 |
276 | module.exports = L.TileLayer.Bing
277 |
--------------------------------------------------------------------------------
/leaflet-bing-layer.js:
--------------------------------------------------------------------------------
1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0; i--) {
17 | var b = 0
18 | var mask = 1 << (i - 1)
19 | if ((x & mask) !== 0) b++
20 | if ((y & mask) !== 0) b += 2
21 | index += b.toString()
22 | }
23 | return index
24 | }
25 |
26 | /**
27 | * Converts Leaflet BBoxString to Bing BBox
28 | * @param {String} bboxString 'southwest_lng,southwest_lat,northeast_lng,northeast_lat'
29 | * @return {Array} [south_lat, west_lng, north_lat, east_lng]
30 | */
31 | function toBingBBox (bboxString) {
32 | var bbox = bboxString.split(',')
33 | return [bbox[1], bbox[0], bbox[3], bbox[2]]
34 | }
35 |
36 | var VALID_IMAGERY_SETS = [
37 | 'Aerial',
38 | 'AerialWithLabels',
39 | 'AerialWithLabelsOnDemand',
40 | 'Road',
41 | 'RoadOnDemand',
42 | 'CanvasLight',
43 | 'CanvasDark',
44 | 'CanvasGray',
45 | 'OrdnanceSurvey'
46 | ]
47 |
48 | var DYNAMIC_IMAGERY_SETS = [
49 | 'AerialWithLabelsOnDemand',
50 | 'RoadOnDemand'
51 | ]
52 |
53 | /**
54 | * Create a new Bing Maps layer.
55 | * @param {string|object} options Either a [Bing Maps Key](https://msdn.microsoft.com/en-us/library/ff428642.aspx) or an options object
56 | * @param {string} options.BingMapsKey A valid Bing Maps Key (required)
57 | * @param {string} [options.imagerySet=Aerial] Type of imagery, see https://msdn.microsoft.com/en-us/library/ff701716.aspx
58 | * @param {string} [options.culture='en-US'] Language for labels, see https://msdn.microsoft.com/en-us/library/hh441729.aspx
59 | * @return {L.TileLayer} A Leaflet TileLayer to add to your map
60 | *
61 | * Create a basic map
62 | * @example
63 | * var map = L.map('map').setView([51.505, -0.09], 13)
64 | * L.TileLayer.Bing(MyBingMapsKey).addTo(map)
65 | */
66 | L.TileLayer.Bing = L.TileLayer.extend({
67 | options: {
68 | bingMapsKey: null, // Required
69 | imagerySet: 'Aerial',
70 | culture: 'en-US',
71 | minZoom: 1,
72 | minNativeZoom: 1,
73 | maxNativeZoom: 19
74 | },
75 |
76 | statics: {
77 | METADATA_URL: 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/{imagerySet}?key={bingMapsKey}&include=ImageryProviders&uriScheme=https',
78 | POINT_METADATA_URL: 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/{imagerySet}/{lat},{lng}?zl={z}&key={bingMapsKey}&uriScheme=https'
79 | },
80 |
81 | initialize: function (options) {
82 | if (typeof options === 'string') {
83 | options = { bingMapsKey: options }
84 | }
85 | if (options && options.BingMapsKey) {
86 | options.bingMapsKey = options.BingMapsKey
87 | console.warn('use options.bingMapsKey instead of options.BingMapsKey')
88 | }
89 | if (!options || !options.bingMapsKey) {
90 | throw new Error('Must supply options.BingMapsKey')
91 | }
92 | options = L.setOptions(this, options)
93 | if (VALID_IMAGERY_SETS.indexOf(options.imagerySet) < 0) {
94 | throw new Error("'" + options.imagerySet + "' is an invalid imagerySet, see https://github.com/digidem/leaflet-bing-layer#parameters")
95 | }
96 | if (options && options.style && DYNAMIC_IMAGERY_SETS.indexOf(options.imagerySet) < 0) {
97 | console.warn('Dynamic styles will only work with these imagerySet choices: ' + DYNAMIC_IMAGERY_SETS.join(', '))
98 | }
99 |
100 | var metaDataUrl = L.Util.template(L.TileLayer.Bing.METADATA_URL, {
101 | bingMapsKey: this.options.bingMapsKey,
102 | imagerySet: this.options.imagerySet
103 | })
104 |
105 | this._imageryProviders = []
106 | this._attributions = []
107 |
108 | // Keep a reference to the promise so we can use it later
109 | this._fetch = fetchJsonp(metaDataUrl, {jsonpCallback: 'jsonp'})
110 | .then(function (response) {
111 | return response.json()
112 | })
113 | .then(this._metaDataOnLoad.bind(this))
114 | .catch(console.error.bind(console))
115 |
116 | // for https://github.com/Leaflet/Leaflet/issues/137
117 | if (!L.Browser.android) {
118 | this.on('tileunload', this._onTileRemove)
119 | }
120 | },
121 |
122 | createTile: function (coords, done) {
123 | var tile = document.createElement('img')
124 |
125 | L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile))
126 | L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile))
127 |
128 | if (this.options.crossOrigin) {
129 | tile.crossOrigin = ''
130 | }
131 |
132 | /*
133 | Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
134 | http://www.w3.org/TR/WCAG20-TECHS/H67
135 | */
136 | tile.alt = ''
137 |
138 | // Don't create closure if we don't have to
139 | if (this._url) {
140 | tile.src = this.getTileUrl(coords)
141 | } else {
142 | this._fetch.then(function () {
143 | tile.src = this.getTileUrl(coords)
144 | }.bind(this)).catch(function (e) {
145 | console.error(e)
146 | done(e)
147 | })
148 | }
149 |
150 | return tile
151 | },
152 |
153 | getTileUrl: function (coords) {
154 | var quadkey = toQuadKey(coords.x, coords.y, coords.z)
155 | var url = L.Util.template(this._url, {
156 | quadkey: quadkey,
157 | subdomain: this._getSubdomain(coords),
158 | culture: this.options.culture
159 | })
160 | if (typeof this.options.style === 'string') {
161 | url += '&st=' + this.options.style
162 | }
163 | return url
164 | },
165 |
166 | // Update the attribution control every time the map is moved
167 | onAdd: function (map) {
168 | map.on('moveend', this._updateAttribution, this)
169 | L.TileLayer.prototype.onAdd.call(this, map)
170 | this._attributions.forEach(function (attribution) {
171 | map.attributionControl.addAttribution(attribution)
172 | })
173 | },
174 |
175 | // Clean up events and remove attributions from attribution control
176 | onRemove: function (map) {
177 | map.off('moveend', this._updateAttribution, this)
178 | this._attributions.forEach(function (attribution) {
179 | map.attributionControl.removeAttribution(attribution)
180 | })
181 | L.TileLayer.prototype.onRemove.call(this, map)
182 | },
183 |
184 | /**
185 | * Get the [Bing Imagery metadata](https://msdn.microsoft.com/en-us/library/ff701712.aspx)
186 | * for a specific [`LatLng`](http://leafletjs.com/reference.html#latlng)
187 | * and zoom level. If either `latlng` or `zoom` is omitted and the layer is attached
188 | * to a map, the map center and current map zoom are used.
189 | * @param {L.LatLng} latlng
190 | * @param {Number} zoom
191 | * @return {Promise} Resolves to the JSON metadata
192 | */
193 | getMetaData: function (latlng, zoom) {
194 | if (!this._map && (!latlng || !zoom)) {
195 | return Promise.reject(new Error('If layer is not attached to map, you must provide LatLng and zoom'))
196 | }
197 | latlng = latlng || this._map.getCenter()
198 | zoom = zoom || this._map.getZoom()
199 | var PointMetaDataUrl = L.Util.template(L.TileLayer.Bing.POINT_METADATA_URL, {
200 | bingMapsKey: this.options.bingMapsKey,
201 | imagerySet: this.options.imagerySet,
202 | z: zoom,
203 | lat: latlng.lat,
204 | lng: latlng.lng
205 | })
206 | return fetchJsonp(PointMetaDataUrl, {jsonpCallback: 'jsonp'})
207 | .then(function (response) {
208 | return response.json()
209 | })
210 | .catch(console.error.bind(console))
211 | },
212 |
213 | _metaDataOnLoad: function (metaData) {
214 | if (metaData.statusCode !== 200) {
215 | throw new Error('Bing Imagery Metadata error: \n' + JSON.stringify(metaData, null, ' '))
216 | }
217 | var resource = metaData.resourceSets[0].resources[0]
218 | this._url = resource.imageUrl
219 | this._imageryProviders = resource.imageryProviders || []
220 | this.options.subdomains = resource.imageUrlSubdomains
221 | this._updateAttribution()
222 | return Promise.resolve()
223 | },
224 |
225 | /**
226 | * Update the attribution control of the map with the provider attributions
227 | * within the current map bounds
228 | */
229 | _updateAttribution: function () {
230 | var map = this._map
231 | if (!map || !map.attributionControl) return
232 | var zoom = map.getZoom()
233 | var bbox = toBingBBox(map.getBounds().toBBoxString())
234 | this._fetch.then(function () {
235 | var newAttributions = this._getAttributions(bbox, zoom)
236 | var prevAttributions = this._attributions
237 | // Add any new provider attributions in the current area to the attribution control
238 | newAttributions.forEach(function (attr) {
239 | if (prevAttributions.indexOf(attr) > -1) return
240 | map.attributionControl.addAttribution(attr)
241 | })
242 | // Remove any attributions that are no longer in the current area from the attribution control
243 | prevAttributions.filter(function (attr) {
244 | if (newAttributions.indexOf(attr) > -1) return
245 | map.attributionControl.removeAttribution(attr)
246 | })
247 | this._attributions = newAttributions
248 | }.bind(this))
249 | },
250 |
251 | /**
252 | * Returns an array of attributions for given bbox and zoom
253 | * @private
254 | * @param {Array} bbox [west, south, east, north]
255 | * @param {Number} zoom
256 | * @return {Array} Array of attribution strings for each provider
257 | */
258 | _getAttributions: function (bbox, zoom) {
259 | return this._imageryProviders.reduce(function (attributions, provider) {
260 | for (var i = 0; i < provider.coverageAreas.length; i++) {
261 | if (bboxIntersect(bbox, provider.coverageAreas[i].bbox) &&
262 | zoom >= provider.coverageAreas[i].zoomMin &&
263 | zoom <= provider.coverageAreas[i].zoomMax) {
264 | attributions.push(provider.attribution)
265 | return attributions
266 | }
267 | }
268 | return attributions
269 | }, [])
270 | }
271 | })
272 |
273 | L.tileLayer.bing = function (options) {
274 | return new L.TileLayer.Bing(options)
275 | }
276 |
277 | module.exports = L.TileLayer.Bing
278 |
279 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
280 | },{"bbox-intersect":2,"fetch-jsonp":3}],2:[function(require,module,exports){
281 | module.exports = function(bbox1, bbox2){
282 | if(!(
283 | bbox1[0] > bbox2[2] ||
284 | bbox1[2] < bbox2[0] ||
285 | bbox1[3] < bbox2[1] ||
286 | bbox1[1] > bbox2[3]
287 | )){
288 | return true;
289 | } else {
290 | return false;
291 | }
292 | }
293 | },{}],3:[function(require,module,exports){
294 | (function (global, factory) {
295 | if (typeof define === 'function' && define.amd) {
296 | define(['exports', 'module'], factory);
297 | } else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
298 | factory(exports, module);
299 | } else {
300 | var mod = {
301 | exports: {}
302 | };
303 | factory(mod.exports, mod);
304 | global.fetchJsonp = mod.exports;
305 | }
306 | })(this, function (exports, module) {
307 | 'use strict';
308 |
309 | var defaultOptions = {
310 | timeout: 5000,
311 | jsonpCallback: 'callback',
312 | jsonpCallbackFunction: null
313 | };
314 |
315 | function generateCallbackFunction() {
316 | return 'jsonp_' + Date.now() + '_' + Math.ceil(Math.random() * 100000);
317 | }
318 |
319 | // Known issue: Will throw 'Uncaught ReferenceError: callback_*** is not defined' error if request timeout
320 | function clearFunction(functionName) {
321 | // IE8 throws an exception when you try to delete a property on window
322 | // http://stackoverflow.com/a/1824228/751089
323 | try {
324 | delete window[functionName];
325 | } catch (e) {
326 | window[functionName] = undefined;
327 | }
328 | }
329 |
330 | function removeScript(scriptId) {
331 | var script = document.getElementById(scriptId);
332 | document.getElementsByTagName('head')[0].removeChild(script);
333 | }
334 |
335 | var fetchJsonp = function fetchJsonp(url) {
336 | var options = arguments[1] === undefined ? {} : arguments[1];
337 |
338 | var timeout = options.timeout != null ? options.timeout : defaultOptions.timeout;
339 | var jsonpCallback = options.jsonpCallback != null ? options.jsonpCallback : defaultOptions.jsonpCallback;
340 |
341 | var timeoutId = undefined;
342 |
343 | return new Promise(function (resolve, reject) {
344 | var callbackFunction = options.jsonpCallbackFunction || generateCallbackFunction();
345 |
346 | window[callbackFunction] = function (response) {
347 | resolve({
348 | ok: true,
349 | // keep consistent with fetch API
350 | json: function json() {
351 | return Promise.resolve(response);
352 | }
353 | });
354 |
355 | if (timeoutId) clearTimeout(timeoutId);
356 |
357 | removeScript(jsonpCallback + '_' + callbackFunction);
358 |
359 | clearFunction(callbackFunction);
360 | };
361 |
362 | // Check if the user set their own params, and if not add a ? to start a list of params
363 | url += url.indexOf('?') === -1 ? '?' : '&';
364 |
365 | var jsonpScript = document.createElement('script');
366 | jsonpScript.setAttribute('src', url + jsonpCallback + '=' + callbackFunction);
367 | jsonpScript.id = jsonpCallback + '_' + callbackFunction;
368 | document.getElementsByTagName('head')[0].appendChild(jsonpScript);
369 |
370 | timeoutId = setTimeout(function () {
371 | reject(new Error('JSONP request to ' + url + ' timed out'));
372 |
373 | clearFunction(callbackFunction);
374 | removeScript(jsonpCallback + '_' + callbackFunction);
375 | }, timeout);
376 | });
377 | };
378 |
379 | // export as global function
380 | /*
381 | let local;
382 | if (typeof global !== 'undefined') {
383 | local = global;
384 | } else if (typeof self !== 'undefined') {
385 | local = self;
386 | } else {
387 | try {
388 | local = Function('return this')();
389 | } catch (e) {
390 | throw new Error('polyfill failed because global object is unavailable in this environment');
391 | }
392 | }
393 |
394 | local.fetchJsonp = fetchJsonp;
395 | */
396 |
397 | module.exports = fetchJsonp;
398 | });
399 | },{}]},{},[1]);
400 |
--------------------------------------------------------------------------------
/leaflet-bing-layer.min.js:
--------------------------------------------------------------------------------
1 | !function t(e,n,i){function o(a,s){if(!n[a]){if(!e[a]){var u="function"==typeof require&&require;if(!s&&u)return u(a,!0);if(r)return r(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var c=n[a]={exports:{}};e[a][0].call(c.exports,function(t){var n=e[a][1][t];return o(n?n:t)},c,c.exports,t,e,n,i)}return n[a].exports}for(var r="function"==typeof require&&require,a=0;a0;o--){var r=0,a=1<-1||t.attributionControl.addAttribution(e)}),o.filter(function(e){i.indexOf(e)>-1||t.attributionControl.removeAttribution(e)}),this._attributions=i}.bind(this))}},_getAttributions:function(t,e){return this._imageryProviders.reduce(function(n,i){for(var o=0;o=i.coverageAreas[o].zoomMin&&e<=i.coverageAreas[o].zoomMax)return n.push(i.attribution),n;return n},[])}}),r.tileLayer.bing=function(t){return new r.TileLayer.Bing(t)},e.exports=r.TileLayer.Bing}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"bbox-intersect":2,"fetch-jsonp":3}],2:[function(t,e,n){e.exports=function(t,e){return t[0]>e[2]||t[2]e[3]?!1:!0}},{}],3:[function(t,e,n){!function(t,i){if("function"==typeof define&&define.amd)define(["exports","module"],i);else if("undefined"!=typeof n&&"undefined"!=typeof e)i(n,e);else{var o={exports:{}};i(o.exports,o),t.fetchJsonp=o.exports}}(this,function(t,e){"use strict";function n(){return"jsonp_"+Date.now()+"_"+Math.ceil(1e5*Math.random())}function i(t){try{delete window[t]}catch(e){window[t]=void 0}}function o(t){var e=document.getElementById(t);document.getElementsByTagName("head")[0].removeChild(e)}var r={timeout:5e3,jsonpCallback:"callback",jsonpCallbackFunction:null},a=function(t){var e=void 0===arguments[1]?{}:arguments[1],a=null!=e.timeout?e.timeout:r.timeout,s=null!=e.jsonpCallback?e.jsonpCallback:r.jsonpCallback,u=void 0;return new Promise(function(r,l){var c=e.jsonpCallbackFunction||n();window[c]=function(t){r({ok:!0,json:function(){return Promise.resolve(t)}}),u&&clearTimeout(u),o(s+"_"+c),i(c)},t+=-1===t.indexOf("?")?"?":"&";var d=document.createElement("script");d.setAttribute("src",t+s+"="+c),d.id=s+"_"+c,document.getElementsByTagName("head")[0].appendChild(d),u=setTimeout(function(){l(new Error("JSONP request to "+t+" timed out")),i(c),o(s+"_"+c)},a)})};e.exports=a})},{}]},{},[1]);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "leaflet-bing-layer",
3 | "version": "3.3.0",
4 | "description": "Bing Maps Layer for Leaflet v1.0.0",
5 | "main": "index.js",
6 | "browserify": {
7 | "transform": [
8 | "browserify-shim"
9 | ]
10 | },
11 | "browserify-shim": {
12 | "leaflet": "global:L"
13 | },
14 | "scripts": {
15 | "build": "browserify index.js > leaflet-bing-layer.js",
16 | "postbuild": "uglifyjs leaflet-bing-layer.js -cm -o leaflet-bing-layer.min.js",
17 | "preversion": "npm test && npm run build",
18 | "lint": "standard index.js",
19 | "start": "budo index.js:leaflet-bing-layer.js --live",
20 | "test": "npm run lint"
21 | },
22 | "keywords": [
23 | "leaflet",
24 | "bing"
25 | ],
26 | "author": "Gregor MacLennan",
27 | "license": "MIT",
28 | "repository": {
29 | "type": "git",
30 | "url": "https://github.com/gmaclennan/leaflet-bing-layer.git"
31 | },
32 | "bugs": {
33 | "url": "https://github.com/gmaclennan/leaflet-bing-layer/issues"
34 | },
35 | "homepage": "https://github.com/gmaclennan/leaflet-bing-layer",
36 | "dependencies": {
37 | "bbox-intersect": "^0.1.1",
38 | "browserify-shim": "^3.8.11",
39 | "fetch-jsonp": "^1.0.0",
40 | "leaflet": "^1.0.0-beta.2"
41 | },
42 | "devDependencies": {
43 | "browserify": "^12.0.1",
44 | "budo": "^7.0.2",
45 | "standard": "^5.4.1"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------