400 lines
13 KiB
JavaScript
400 lines
13 KiB
JavaScript
(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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
(function (global){
|
|
var L = (typeof window !== "undefined" ? window['L'] : typeof global !== "undefined" ? global['L'] : null)
|
|
var fetchJsonp = require('fetch-jsonp')
|
|
var bboxIntersect = require('bbox-intersect')
|
|
|
|
/**
|
|
* Converts tile xyz coordinates to Quadkey
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} z
|
|
* @return {Number} Quadkey
|
|
*/
|
|
function toQuadKey (x, y, z) {
|
|
var index = ''
|
|
for (var i = z; i > 0; i--) {
|
|
var b = 0
|
|
var mask = 1 << (i - 1)
|
|
if ((x & mask) !== 0) b++
|
|
if ((y & mask) !== 0) b += 2
|
|
index += b.toString()
|
|
}
|
|
return index
|
|
}
|
|
|
|
/**
|
|
* Converts Leaflet BBoxString to Bing BBox
|
|
* @param {String} bboxString 'southwest_lng,southwest_lat,northeast_lng,northeast_lat'
|
|
* @return {Array} [south_lat, west_lng, north_lat, east_lng]
|
|
*/
|
|
function toBingBBox (bboxString) {
|
|
var bbox = bboxString.split(',')
|
|
return [bbox[1], bbox[0], bbox[3], bbox[2]]
|
|
}
|
|
|
|
var VALID_IMAGERY_SETS = [
|
|
'Aerial',
|
|
'AerialWithLabels',
|
|
'AerialWithLabelsOnDemand',
|
|
'Road',
|
|
'RoadOnDemand',
|
|
'CanvasLight',
|
|
'CanvasDark',
|
|
'CanvasGray',
|
|
'OrdnanceSurvey'
|
|
]
|
|
|
|
var DYNAMIC_IMAGERY_SETS = [
|
|
'AerialWithLabelsOnDemand',
|
|
'RoadOnDemand'
|
|
]
|
|
|
|
/**
|
|
* Create a new Bing Maps layer.
|
|
* @param {string|object} options Either a [Bing Maps Key](https://msdn.microsoft.com/en-us/library/ff428642.aspx) or an options object
|
|
* @param {string} options.BingMapsKey A valid Bing Maps Key (required)
|
|
* @param {string} [options.imagerySet=Aerial] Type of imagery, see https://msdn.microsoft.com/en-us/library/ff701716.aspx
|
|
* @param {string} [options.culture='en-US'] Language for labels, see https://msdn.microsoft.com/en-us/library/hh441729.aspx
|
|
* @return {L.TileLayer} A Leaflet TileLayer to add to your map
|
|
*
|
|
* Create a basic map
|
|
* @example
|
|
* var map = L.map('map').setView([51.505, -0.09], 13)
|
|
* L.TileLayer.Bing(MyBingMapsKey).addTo(map)
|
|
*/
|
|
L.TileLayer.Bing = L.TileLayer.extend({
|
|
options: {
|
|
bingMapsKey: null, // Required
|
|
imagerySet: 'Aerial',
|
|
culture: 'en-US',
|
|
minZoom: 1,
|
|
minNativeZoom: 1,
|
|
maxNativeZoom: 19
|
|
},
|
|
|
|
statics: {
|
|
METADATA_URL: 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/{imagerySet}?key={bingMapsKey}&include=ImageryProviders&uriScheme=https',
|
|
POINT_METADATA_URL: 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/{imagerySet}/{lat},{lng}?zl={z}&key={bingMapsKey}&uriScheme=https'
|
|
},
|
|
|
|
initialize: function (options) {
|
|
if (typeof options === 'string') {
|
|
options = { bingMapsKey: options }
|
|
}
|
|
if (options && options.BingMapsKey) {
|
|
options.bingMapsKey = options.BingMapsKey
|
|
console.warn('use options.bingMapsKey instead of options.BingMapsKey')
|
|
}
|
|
if (!options || !options.bingMapsKey) {
|
|
throw new Error('Must supply options.BingMapsKey')
|
|
}
|
|
options = L.setOptions(this, options)
|
|
if (VALID_IMAGERY_SETS.indexOf(options.imagerySet) < 0) {
|
|
throw new Error("'" + options.imagerySet + "' is an invalid imagerySet, see https://github.com/digidem/leaflet-bing-layer#parameters")
|
|
}
|
|
if (options && options.style && DYNAMIC_IMAGERY_SETS.indexOf(options.imagerySet) < 0) {
|
|
console.warn('Dynamic styles will only work with these imagerySet choices: ' + DYNAMIC_IMAGERY_SETS.join(', '))
|
|
}
|
|
|
|
var metaDataUrl = L.Util.template(L.TileLayer.Bing.METADATA_URL, {
|
|
bingMapsKey: this.options.bingMapsKey,
|
|
imagerySet: this.options.imagerySet
|
|
})
|
|
|
|
this._imageryProviders = []
|
|
this._attributions = []
|
|
|
|
// Keep a reference to the promise so we can use it later
|
|
this._fetch = fetchJsonp(metaDataUrl, {jsonpCallback: 'jsonp'})
|
|
.then(function (response) {
|
|
return response.json()
|
|
})
|
|
.then(this._metaDataOnLoad.bind(this))
|
|
.catch(console.error.bind(console))
|
|
|
|
// for https://github.com/Leaflet/Leaflet/issues/137
|
|
if (!L.Browser.android) {
|
|
this.on('tileunload', this._onTileRemove)
|
|
}
|
|
},
|
|
|
|
createTile: function (coords, done) {
|
|
var tile = document.createElement('img')
|
|
|
|
L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile))
|
|
L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile))
|
|
|
|
if (this.options.crossOrigin) {
|
|
tile.crossOrigin = ''
|
|
}
|
|
|
|
/*
|
|
Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
|
|
http://www.w3.org/TR/WCAG20-TECHS/H67
|
|
*/
|
|
tile.alt = ''
|
|
|
|
// Don't create closure if we don't have to
|
|
if (this._url) {
|
|
tile.src = this.getTileUrl(coords)
|
|
} else {
|
|
this._fetch.then(function () {
|
|
tile.src = this.getTileUrl(coords)
|
|
}.bind(this)).catch(function (e) {
|
|
console.error(e)
|
|
done(e)
|
|
})
|
|
}
|
|
|
|
return tile
|
|
},
|
|
|
|
getTileUrl: function (coords) {
|
|
var quadkey = toQuadKey(coords.x, coords.y, coords.z)
|
|
var url = L.Util.template(this._url, {
|
|
quadkey: quadkey,
|
|
subdomain: this._getSubdomain(coords),
|
|
culture: this.options.culture
|
|
})
|
|
if (typeof this.options.style === 'string') {
|
|
url += '&st=' + this.options.style
|
|
}
|
|
return url
|
|
},
|
|
|
|
// Update the attribution control every time the map is moved
|
|
onAdd: function (map) {
|
|
map.on('moveend', this._updateAttribution, this)
|
|
L.TileLayer.prototype.onAdd.call(this, map)
|
|
this._attributions.forEach(function (attribution) {
|
|
map.attributionControl.addAttribution(attribution)
|
|
})
|
|
},
|
|
|
|
// Clean up events and remove attributions from attribution control
|
|
onRemove: function (map) {
|
|
map.off('moveend', this._updateAttribution, this)
|
|
this._attributions.forEach(function (attribution) {
|
|
map.attributionControl.removeAttribution(attribution)
|
|
})
|
|
L.TileLayer.prototype.onRemove.call(this, map)
|
|
},
|
|
|
|
/**
|
|
* 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. If either `latlng` or `zoom` is omitted and the layer is attached
|
|
* to a map, the map center and current map zoom are used.
|
|
* @param {L.LatLng} latlng
|
|
* @param {Number} zoom
|
|
* @return {Promise} Resolves to the JSON metadata
|
|
*/
|
|
getMetaData: function (latlng, zoom) {
|
|
if (!this._map && (!latlng || !zoom)) {
|
|
return Promise.reject(new Error('If layer is not attached to map, you must provide LatLng and zoom'))
|
|
}
|
|
latlng = latlng || this._map.getCenter()
|
|
zoom = zoom || this._map.getZoom()
|
|
var PointMetaDataUrl = L.Util.template(L.TileLayer.Bing.POINT_METADATA_URL, {
|
|
bingMapsKey: this.options.bingMapsKey,
|
|
imagerySet: this.options.imagerySet,
|
|
z: zoom,
|
|
lat: latlng.lat,
|
|
lng: latlng.lng
|
|
})
|
|
return fetchJsonp(PointMetaDataUrl, {jsonpCallback: 'jsonp'})
|
|
.then(function (response) {
|
|
return response.json()
|
|
})
|
|
.catch(console.error.bind(console))
|
|
},
|
|
|
|
_metaDataOnLoad: function (metaData) {
|
|
if (metaData.statusCode !== 200) {
|
|
throw new Error('Bing Imagery Metadata error: \n' + JSON.stringify(metaData, null, ' '))
|
|
}
|
|
var resource = metaData.resourceSets[0].resources[0]
|
|
this._url = resource.imageUrl
|
|
this._imageryProviders = resource.imageryProviders || []
|
|
this.options.subdomains = resource.imageUrlSubdomains
|
|
this._updateAttribution()
|
|
return Promise.resolve()
|
|
},
|
|
|
|
/**
|
|
* Update the attribution control of the map with the provider attributions
|
|
* within the current map bounds
|
|
*/
|
|
_updateAttribution: function () {
|
|
var map = this._map
|
|
if (!map || !map.attributionControl) return
|
|
var zoom = map.getZoom()
|
|
var bbox = toBingBBox(map.getBounds().toBBoxString())
|
|
this._fetch.then(function () {
|
|
var newAttributions = this._getAttributions(bbox, zoom)
|
|
var prevAttributions = this._attributions
|
|
// Add any new provider attributions in the current area to the attribution control
|
|
newAttributions.forEach(function (attr) {
|
|
if (prevAttributions.indexOf(attr) > -1) return
|
|
map.attributionControl.addAttribution(attr)
|
|
})
|
|
// Remove any attributions that are no longer in the current area from the attribution control
|
|
prevAttributions.filter(function (attr) {
|
|
if (newAttributions.indexOf(attr) > -1) return
|
|
map.attributionControl.removeAttribution(attr)
|
|
})
|
|
this._attributions = newAttributions
|
|
}.bind(this))
|
|
},
|
|
|
|
/**
|
|
* Returns an array of attributions for given bbox and zoom
|
|
* @private
|
|
* @param {Array} bbox [west, south, east, north]
|
|
* @param {Number} zoom
|
|
* @return {Array} Array of attribution strings for each provider
|
|
*/
|
|
_getAttributions: function (bbox, zoom) {
|
|
return this._imageryProviders.reduce(function (attributions, provider) {
|
|
for (var i = 0; i < provider.coverageAreas.length; i++) {
|
|
if (bboxIntersect(bbox, provider.coverageAreas[i].bbox) &&
|
|
zoom >= provider.coverageAreas[i].zoomMin &&
|
|
zoom <= provider.coverageAreas[i].zoomMax) {
|
|
attributions.push(provider.attribution)
|
|
return attributions
|
|
}
|
|
}
|
|
return attributions
|
|
}, [])
|
|
}
|
|
})
|
|
|
|
L.tileLayer.bing = function (options) {
|
|
return new L.TileLayer.Bing(options)
|
|
}
|
|
|
|
module.exports = L.TileLayer.Bing
|
|
|
|
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
},{"bbox-intersect":2,"fetch-jsonp":3}],2:[function(require,module,exports){
|
|
module.exports = function(bbox1, bbox2){
|
|
if(!(
|
|
bbox1[0] > bbox2[2] ||
|
|
bbox1[2] < bbox2[0] ||
|
|
bbox1[3] < bbox2[1] ||
|
|
bbox1[1] > bbox2[3]
|
|
)){
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
},{}],3:[function(require,module,exports){
|
|
(function (global, factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
define(['exports', 'module'], factory);
|
|
} else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
|
|
factory(exports, module);
|
|
} else {
|
|
var mod = {
|
|
exports: {}
|
|
};
|
|
factory(mod.exports, mod);
|
|
global.fetchJsonp = mod.exports;
|
|
}
|
|
})(this, function (exports, module) {
|
|
'use strict';
|
|
|
|
var defaultOptions = {
|
|
timeout: 5000,
|
|
jsonpCallback: 'callback',
|
|
jsonpCallbackFunction: null
|
|
};
|
|
|
|
function generateCallbackFunction() {
|
|
return 'jsonp_' + Date.now() + '_' + Math.ceil(Math.random() * 100000);
|
|
}
|
|
|
|
// Known issue: Will throw 'Uncaught ReferenceError: callback_*** is not defined' error if request timeout
|
|
function clearFunction(functionName) {
|
|
// IE8 throws an exception when you try to delete a property on window
|
|
// http://stackoverflow.com/a/1824228/751089
|
|
try {
|
|
delete window[functionName];
|
|
} catch (e) {
|
|
window[functionName] = undefined;
|
|
}
|
|
}
|
|
|
|
function removeScript(scriptId) {
|
|
var script = document.getElementById(scriptId);
|
|
document.getElementsByTagName('head')[0].removeChild(script);
|
|
}
|
|
|
|
var fetchJsonp = function fetchJsonp(url) {
|
|
var options = arguments[1] === undefined ? {} : arguments[1];
|
|
|
|
var timeout = options.timeout != null ? options.timeout : defaultOptions.timeout;
|
|
var jsonpCallback = options.jsonpCallback != null ? options.jsonpCallback : defaultOptions.jsonpCallback;
|
|
|
|
var timeoutId = undefined;
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
var callbackFunction = options.jsonpCallbackFunction || generateCallbackFunction();
|
|
|
|
window[callbackFunction] = function (response) {
|
|
resolve({
|
|
ok: true,
|
|
// keep consistent with fetch API
|
|
json: function json() {
|
|
return Promise.resolve(response);
|
|
}
|
|
});
|
|
|
|
if (timeoutId) clearTimeout(timeoutId);
|
|
|
|
removeScript(jsonpCallback + '_' + callbackFunction);
|
|
|
|
clearFunction(callbackFunction);
|
|
};
|
|
|
|
// Check if the user set their own params, and if not add a ? to start a list of params
|
|
url += url.indexOf('?') === -1 ? '?' : '&';
|
|
|
|
var jsonpScript = document.createElement('script');
|
|
jsonpScript.setAttribute('src', url + jsonpCallback + '=' + callbackFunction);
|
|
jsonpScript.id = jsonpCallback + '_' + callbackFunction;
|
|
document.getElementsByTagName('head')[0].appendChild(jsonpScript);
|
|
|
|
timeoutId = setTimeout(function () {
|
|
reject(new Error('JSONP request to ' + url + ' timed out'));
|
|
|
|
clearFunction(callbackFunction);
|
|
removeScript(jsonpCallback + '_' + callbackFunction);
|
|
}, timeout);
|
|
});
|
|
};
|
|
|
|
// export as global function
|
|
/*
|
|
let local;
|
|
if (typeof global !== 'undefined') {
|
|
local = global;
|
|
} else if (typeof self !== 'undefined') {
|
|
local = self;
|
|
} else {
|
|
try {
|
|
local = Function('return this')();
|
|
} catch (e) {
|
|
throw new Error('polyfill failed because global object is unavailable in this environment');
|
|
}
|
|
}
|
|
|
|
local.fetchJsonp = fetchJsonp;
|
|
*/
|
|
|
|
module.exports = fetchJsonp;
|
|
});
|
|
},{}]},{},[1]);
|