L.ajax = function (url, success, error) { // the following is from JavaScript: The Definitive Guide // and https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest_in_IE6 if (window.XMLHttpRequest === undefined) { window.XMLHttpRequest = function () { /*global ActiveXObject:true */ try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { throw new Error("XMLHttpRequest is not supported"); } }; } var response, request = new XMLHttpRequest(); request.open("GET", url); request.onreadystatechange = function () { /*jshint evil: true */ if (request.readyState === 4) { if (request.status === 200) { if (window.JSON) { response = JSON.parse(request.responseText); } else { response = eval("(" + request.responseText + ")"); } success(response); } else if (request.status !== 0 && error !== undefined) { error(request.status); } } }; request.ontimeout = function () { error('timeout'); }; request.send(); return request; }; L.UtfGrid = (L.Layer || L.Class).extend({ includes: L.Evented, options: { subdomains: 'abc', minZoom: 0, maxZoom: 18, tileSize: 256, resolution: 4, useJsonP: true, pointerCursor: true, maxRequests: 4, requestTimeout: 60000 }, //The thing the mouse is currently on _mouseOn: null, initialize: function (url, options) { L.Util.setOptions(this, options); // The requests this._requests = {}; this._request_queue = []; this._requests_in_process = []; this._url = url; this._cache = {}; //Find a unique id in window we can use for our callbacks //Required for jsonP var i = 0; while (window['lu' + i]) { i++; } this._windowKey = 'lu' + i; window[this._windowKey] = {}; var subdomains = this.options.subdomains; if (typeof this.options.subdomains === 'string') { this.options.subdomains = subdomains.split(''); } }, onAdd: function (map) { this._map = map; this._container = this._map._container; this._update(); var zoom = Math.round(this._map.getZoom()); if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { return; } map.on('click', this._click, this); map.on('mousemove', this._move, this); map.on('moveend', this._update, this); }, onRemove: function () { var map = this._map; map.off('click', this._click, this); map.off('mousemove', this._move, this); map.off('moveend', this._update, this); if (this.options.pointerCursor) { this._container.style.cursor = ''; } }, setUrl: function (url, noRedraw) { this._url = url; if (!noRedraw) { this.redraw(); } return this; }, redraw: function () { // Clear cache to force all tiles to reload this._request_queue = []; for (var req_key in this._requests) { if (this._requests.hasOwnProperty(req_key)) { this._abort_request(req_key); } } this._cache = {}; this._update(); }, _click: function (e) { this.fire('click', this._objectForEvent(e)); }, _move: function (e) { var on = this._objectForEvent(e); if (on.data !== this._mouseOn) { if (this._mouseOn) { this.fire('mouseout', { latlng: e.latlng, data: this._mouseOn }); if (this.options.pointerCursor) { this._container.style.cursor = ''; } } if (on.data) { this.fire('mouseover', on); if (this.options.pointerCursor) { this._container.style.cursor = 'pointer'; } } this._mouseOn = on.data; } else if (on.data) { this.fire('mousemove', on); } }, _objectForEvent: function (e) { var map = this._map, point = map.project(e.latlng), tileSize = this.options.tileSize, resolution = this.options.resolution, x = Math.floor(point.x / tileSize), y = Math.floor(point.y / tileSize), gridX = Math.floor((point.x - (x * tileSize)) / resolution), gridY = Math.floor((point.y - (y * tileSize)) / resolution), max = map.options.crs.scale(map.getZoom()) / tileSize; x = (x + max) % max; y = (y + max) % max; var data = this._cache[map.getZoom() + '_' + x + '_' + y]; var result = null; if (data && data.grid) { var idx = this._utfDecode(data.grid[gridY].charCodeAt(gridX)), key = data.keys[idx]; if (data.data.hasOwnProperty(key)) { result = data.data[key]; } } return L.extend({ latlng: e.latlng, data: result }, e); }, //Load up all required json grid files //TODO: Load from center etc _update: function () { var bounds = this._map.getPixelBounds(), zoom = Math.round(this._map.getZoom()), tileSize = this.options.tileSize; if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { return; } var nwTilePoint = new L.Point( Math.floor(bounds.min.x / tileSize), Math.floor(bounds.min.y / tileSize)), seTilePoint = new L.Point( Math.floor(bounds.max.x / tileSize), Math.floor(bounds.max.y / tileSize)), max = this._map.options.crs.scale(zoom) / tileSize; //Load all required ones var visible_tiles = []; for (var x = nwTilePoint.x; x <= seTilePoint.x; x++) { for (var y = nwTilePoint.y; y <= seTilePoint.y; y++) { var xw = (x + max) % max, yw = (y + max) % max; var key = zoom + '_' + xw + '_' + yw; visible_tiles.push(key); if (!this._cache.hasOwnProperty(key)) { this._cache[key] = null; if (this.options.useJsonP) { this._loadTileP(zoom, xw, yw); } else { this._loadTile(zoom, xw, yw); } } } } // If we still have requests for tiles that have now gone out of sight, attempt to abort them. for (var req_key in this._requests) { if (visible_tiles.indexOf(req_key) < 0) { this._abort_request(req_key); } } }, _loadTileP: function (zoom, x, y) { var head = document.getElementsByTagName('head')[0], key = zoom + '_' + x + '_' + y, functionName = 'lu_' + key, wk = this._windowKey, self = this; var url = L.Util.template(this._url, L.Util.extend({ s: L.TileLayer.prototype._getSubdomain.call(this, { x: x, y: y }), z: zoom, x: x, y: y, cb: wk + '.' + functionName }, this.options)); var script = document.createElement('script'); script.setAttribute("type", "text/javascript"); script.setAttribute("src", url); window[wk][functionName] = function (data) { self._cache[key] = data; delete window[wk][functionName]; if (script.parentElement===head) { head.removeChild(script); } self._finish_request(key); }; this._queue_request(key, url, function () { head.appendChild(script); return { abort: function () { head.removeChild(script); } }; }); }, _loadTile: function (zoom, x, y) { var url = L.Util.template(this._url, L.Util.extend({ s: L.TileLayer.prototype._getSubdomain.call(this, { x: x, y: y }), z: zoom, x: x, y: y }, this.options)); var key = zoom + '_' + x + '_' + y; this._queue_request(key, url, this._ajaxRequestFactory(key, url)); }, _ajaxRequestFactory: function (key, url) { var successCallback = this._successCallbackFactory(key); var errorCallback = this._errorCallbackFactory(url); return function () { var request = L.ajax(url, successCallback, errorCallback); request.timeout = this.options.requestTimeout; return request; }.bind(this); }, _successCallbackFactory: function (key) { return function (data) { this._cache[key] = data; this._finish_request(key); }.bind(this); }, _errorCallbackFactory: function (tileurl) { return function (statuscode) { this.fire('tileerror', { url: tileurl, code: statuscode }); }.bind(this); }, _queue_request: function (key, url, callback) { this._requests[key] = { callback: callback, timeout: null, handler: null, url: url }; this._request_queue.push(key); this._process_queued_requests(); }, _finish_request: function (key) { // Remove from requests in process var pos = this._requests_in_process.indexOf(key); if (pos >= 0) { this._requests_in_process.splice(pos, 1); } // Remove from request queue pos = this._request_queue.indexOf(key); if (pos >= 0) { this._request_queue.splice(pos, 1); } // Remove the request entry if (this._requests[key]) { if (this._requests[key].timeout) { window.clearTimeout(this._requests[key].timeout); } delete this._requests[key]; } // Recurse this._process_queued_requests(); // Fire 'load' event if all tiles have been loaded if (this._requests_in_process.length === 0) { this.fire('load'); } }, _abort_request: function (key) { // Abort the request if possible if (this._requests[key] && this._requests[key].handler) { if (typeof this._requests[key].handler.abort === 'function') { this._requests[key].handler.abort(); } } // Ensure we don't keep a false copy of the data in the cache if (this._cache[key] === null) { delete this._cache[key]; } // And remove the request this._finish_request(key); }, _process_queued_requests: function () { while (this._request_queue.length > 0 && (this.options.maxRequests === 0 || this._requests_in_process.length < this.options.maxRequests)) { this._process_request(this._request_queue.pop()); } }, _process_request: function (key) { this._requests_in_process.push(key); // The callback might call _finish_request, so don't assume _requests[key] still exists. var handler = this._requests[key].callback(); if (this._requests[key]) { this._requests[key].handler = handler; if (handler.timeout === undefined) { var timeoutCallback = this._timeoutCallbackFactory(key); this._requests[key].timeout = window.setTimeout(timeoutCallback, this.options.requestTimeout); } } }, _timeoutCallbackFactory: function (key) { var tileurl = this._requests[key].url; return function () { this.fire('tileerror', { url: tileurl, code: 'timeout' }); this._abort_request(key); }.bind(this); }, _utfDecode: function (c) { if (c >= 93) { c--; } if (c >= 35) { c--; } return c - 32; } }); L.utfGrid = function (url, options) { return new L.UtfGrid(url, options); };