Merge pull request #301 from ahocevar/offline
Adding CacheRead and CacheWrite controls. r=@elemoine,@fredj
This commit is contained in:
156
lib/OpenLayers/Control/CacheRead.js
Normal file
156
lib/OpenLayers/Control/CacheRead.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
|
||||
* full list of contributors). Published under the Clear BSD license.
|
||||
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the
|
||||
* full text of the license. */
|
||||
|
||||
/**
|
||||
* @requires OpenLayers/Control.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class: OpenLayers.Control.CacheRead
|
||||
* A control for using image tiles cached with <OpenLayers.Control.CacheWrite>
|
||||
* from the browser's local storage.
|
||||
*
|
||||
* Inherits from:
|
||||
* - <OpenLayers.Control>
|
||||
*/
|
||||
OpenLayers.Control.CacheRead = OpenLayers.Class(OpenLayers.Control, {
|
||||
|
||||
/**
|
||||
* APIProperty: fetchEvent
|
||||
* {String} The layer event to listen to for replacing remote resource tile
|
||||
* URLs with cached data URIs. Supported values are "tileerror" (try
|
||||
* remote first, fall back to cached) and "tileloadstart" (try cache
|
||||
* first, fall back to remote). Default is "tileloadstart".
|
||||
*
|
||||
* Note that "tileerror" will not work for CORS enabled images (see
|
||||
* https://developer.mozilla.org/en/CORS_Enabled_Image), i.e. layers
|
||||
* configured with a <OpenLayers.Tile.Image.crossOriginKeyword> in
|
||||
* <OpenLayers.Layer.Grid.tileOptions>.
|
||||
*/
|
||||
fetchEvent: "tileloadstart",
|
||||
|
||||
/**
|
||||
* APIProperty: layers
|
||||
* {Array(<OpenLayers.Layer.Grid>)}. Optional. If provided, only these
|
||||
* layers will receive tiles from the cache.
|
||||
*/
|
||||
layers: null,
|
||||
|
||||
/**
|
||||
* APIProperty: autoActivate
|
||||
* {Boolean} Activate the control when it is added to a map. Default is
|
||||
* true.
|
||||
*/
|
||||
autoActivate: true,
|
||||
|
||||
/**
|
||||
* Constructor: OpenLayers.Control.CacheRead
|
||||
*
|
||||
* Parameters:
|
||||
* options - {Object} Object with API properties for this control
|
||||
*/
|
||||
|
||||
/**
|
||||
* Method: setMap
|
||||
* Set the map property for the control.
|
||||
*
|
||||
* Parameters:
|
||||
* map - {<OpenLayers.Map>}
|
||||
*/
|
||||
setMap: function(map) {
|
||||
OpenLayers.Control.prototype.setMap.apply(this, arguments);
|
||||
var i, layers = this.layers || map.layers;
|
||||
for (i=layers.length-1; i>=0; --i) {
|
||||
this.addLayer({layer: layers[i]});
|
||||
}
|
||||
if (!this.layers) {
|
||||
map.events.on({
|
||||
addlayer: this.addLayer,
|
||||
removeLayer: this.removeLayer,
|
||||
scope: this
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: addLayer
|
||||
* Adds a layer to the control. Once added, tiles requested for this layer
|
||||
* will be cached.
|
||||
*
|
||||
* Parameters:
|
||||
* evt - {Object} Object with a layer property referencing an
|
||||
* <OpenLayers.Layer> instance
|
||||
*/
|
||||
addLayer: function(evt) {
|
||||
evt.layer.events.register(this.fetchEvent, this, this.fetch);
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: removeLayer
|
||||
* Removes a layer from the control. Once removed, tiles requested for this
|
||||
* layer will no longer be cached.
|
||||
*
|
||||
* Parameters:
|
||||
* evt - {Object} Object with a layer property referencing an
|
||||
* <OpenLayers.Layer> instance
|
||||
*/
|
||||
removeLayer: function(evt) {
|
||||
evt.layer.events.unregister(this.fetchEvent, this, this.fetch);
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: fetch
|
||||
* Listener to the <fetchEvent> event. Replaces a tile's url with a data
|
||||
* URI from the cache.
|
||||
*
|
||||
* Parameters:
|
||||
* evt - {Object} Event object with a tile property.
|
||||
*/
|
||||
fetch: function(evt) {
|
||||
if (this.active && window.localStorage &&
|
||||
evt.tile instanceof OpenLayers.Tile.Image) {
|
||||
var tile = evt.tile,
|
||||
url = tile.url;
|
||||
// deal with modified tile urls when both CacheWrite and CacheRead
|
||||
// are active
|
||||
if (!tile.layer.crossOriginKeyword && OpenLayers.ProxyHost &&
|
||||
url.indexOf(OpenLayers.ProxyHost) === 0) {
|
||||
url = OpenLayers.Control.CacheWrite.urlMap[url];
|
||||
}
|
||||
var dataURI = window.localStorage.getItem("olCache_" + tile.url);
|
||||
if (dataURI) {
|
||||
tile.url = dataURI;
|
||||
if (evt.type === "tileerror") {
|
||||
tile.setImgSrc(dataURI);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: destroy
|
||||
* The destroy method is used to perform any clean up before the control
|
||||
* is dereferenced. Typically this is where event listeners are removed
|
||||
* to prevent memory leaks.
|
||||
*/
|
||||
destroy: function() {
|
||||
if (this.layers || this.map) {
|
||||
var i, layers = this.layers || this.map.layers;
|
||||
for (i=layers.length-1; i>=0; --i) {
|
||||
this.removeLayer({layer: layers[i]});
|
||||
}
|
||||
}
|
||||
if (this.map) {
|
||||
this.map.events.un({
|
||||
addlayer: this.addLayer,
|
||||
removeLayer: this.removeLayer,
|
||||
scope: this
|
||||
});
|
||||
}
|
||||
OpenLayers.Control.prototype.destroy.apply(this, arguments);
|
||||
},
|
||||
|
||||
CLASS_NAME: "OpenLayers.Control.CacheRead"
|
||||
});
|
||||
243
lib/OpenLayers/Control/CacheWrite.js
Normal file
243
lib/OpenLayers/Control/CacheWrite.js
Normal file
@@ -0,0 +1,243 @@
|
||||
/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
|
||||
* full list of contributors). Published under the Clear BSD license.
|
||||
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the
|
||||
* full text of the license. */
|
||||
|
||||
/**
|
||||
* @requires OpenLayers/Control.js
|
||||
* @requires OpenLayers/Request.js
|
||||
* @requires OpenLayers/Console.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class: OpenLayers.Control.CacheWrite
|
||||
* A control for caching image tiles in the browser's local storage. The
|
||||
* <OpenLayers.Control.CacheRead> control is used to fetch and use the cached
|
||||
* tile images.
|
||||
*
|
||||
* Note: Before using this control on any layer that is not your own, make sure
|
||||
* that the terms of service of the tile provider allow local storage of tiles.
|
||||
*
|
||||
* Inherits from:
|
||||
* - <OpenLayers.Control>
|
||||
*/
|
||||
OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, {
|
||||
|
||||
/**
|
||||
* APIProperty: events
|
||||
* {<OpenLayers.Events>} Events instance for listeners and triggering
|
||||
* control specific events.
|
||||
*
|
||||
* To register events in the constructor, configure <eventListeners>.
|
||||
*
|
||||
* Register a listener for a particular event with the following syntax:
|
||||
* (code)
|
||||
* control.events.register(type, obj, listener);
|
||||
* (end)
|
||||
*
|
||||
* Supported event types (in addition to those from <OpenLayers.Control.events>):
|
||||
* cachefull - Triggered when the cache is full. Listeners receive an
|
||||
* object with a tile property as first argument. The tile references
|
||||
* the tile that couldn't be cached.
|
||||
*/
|
||||
|
||||
/**
|
||||
* APIProperty: eventListeners
|
||||
* {Object} Object with event listeners, keyed by event name. An optional
|
||||
* scope property defines the scope that listeners will be executed in.
|
||||
*/
|
||||
|
||||
/**
|
||||
* APIProperty: layers
|
||||
* {Array(<OpenLayers.Layer.Grid>)}. Optional. If provided, caching
|
||||
* will be enabled for these layers only, otherwise for all cacheable
|
||||
* layers.
|
||||
*/
|
||||
layers: null,
|
||||
|
||||
/**
|
||||
* APIProperty: imageFormat
|
||||
* {String} The image format used for caching. The default is "image/png".
|
||||
* Supported formats depend on the user agent. If an unsupported
|
||||
* <imageFormat> is provided, "image/png" will be used. For aerial
|
||||
* imagery, "image/jpeg" is recommended.
|
||||
*/
|
||||
imageFormat: "image/png",
|
||||
|
||||
/**
|
||||
* Property: quotaRegEx
|
||||
* {RegExp}
|
||||
*/
|
||||
quotaRegEx: (/quota/i),
|
||||
|
||||
/**
|
||||
* Constructor: OpenLayers.Control.CacheWrite
|
||||
*
|
||||
* Parameters:
|
||||
* options - {Object} Object with API properties for this control.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Method: setMap
|
||||
* Set the map property for the control.
|
||||
*
|
||||
* Parameters:
|
||||
* map - {<OpenLayers.Map>}
|
||||
*/
|
||||
setMap: function(map) {
|
||||
OpenLayers.Control.prototype.setMap.apply(this, arguments);
|
||||
var i, layers = this.layers || map.layers;
|
||||
for (i=layers.length-1; i>=0; --i) {
|
||||
this.addLayer({layer: layers[i]});
|
||||
}
|
||||
if (!this.layers) {
|
||||
map.events.on({
|
||||
addlayer: this.addLayer,
|
||||
removeLayer: this.removeLayer,
|
||||
scope: this
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: addLayer
|
||||
* Adds a layer to the control. Once added, tiles requested for this layer
|
||||
* will be cached.
|
||||
*
|
||||
* Parameters:
|
||||
* evt - {Object} Object with a layer property referencing an
|
||||
* <OpenLayers.Layer> instance
|
||||
*/
|
||||
addLayer: function(evt) {
|
||||
evt.layer.events.on({
|
||||
tileloadstart: this.makeSameOrigin,
|
||||
tileloaded: this.cache,
|
||||
scope: this
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: removeLayer
|
||||
* Removes a layer from the control. Once removed, tiles requested for this
|
||||
* layer will no longer be cached.
|
||||
*
|
||||
* Parameters:
|
||||
* evt - {Object} Object with a layer property referencing an
|
||||
* <OpenLayers.Layer> instance
|
||||
*/
|
||||
removeLayer: function(evt) {
|
||||
evt.layer.events.un({
|
||||
tileloadstart: this.makeSameOrigin,
|
||||
tileloaded: this.cache,
|
||||
scope: this
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: makeSameOrigin
|
||||
* If the tile does not have CORS image loading enabled and is from a
|
||||
* different origin, use OpenLayers.ProxyHost to make it a same origin url.
|
||||
*
|
||||
* Parameters:
|
||||
* evt - {<OpenLayers.Event>}
|
||||
*/
|
||||
makeSameOrigin: function(evt) {
|
||||
if (this.active) {
|
||||
var tile = evt.tile;
|
||||
if (tile instanceof OpenLayers.Tile.Image &&
|
||||
!tile.crossOriginKeyword &&
|
||||
tile.url.substr(0, 5) !== "data:") {
|
||||
var sameOriginUrl = OpenLayers.Request.makeSameOrigin(
|
||||
tile.url, OpenLayers.ProxyHost
|
||||
);
|
||||
OpenLayers.Control.CacheWrite.urlMap[sameOriginUrl] = tile.url;
|
||||
tile.url = sameOriginUrl;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: cache
|
||||
* Adds a tile to the cache. When the cache is full, the "cachefull" event
|
||||
* is triggered.
|
||||
*
|
||||
* Parameters:
|
||||
* obj - {Object} Object with a tile property, tile being the
|
||||
* <OpenLayers.Tile.Image> with the data to add to the cache
|
||||
*/
|
||||
cache: function(obj) {
|
||||
if (this.active && window.localStorage) {
|
||||
var tile = obj.tile;
|
||||
if (tile instanceof OpenLayers.Tile.Image &&
|
||||
tile.url.substr(0, 5) !== 'data:') {
|
||||
try {
|
||||
var canvasContext = tile.getCanvasContext();
|
||||
if (canvasContext) {
|
||||
window.localStorage.setItem(
|
||||
"olCache_" + OpenLayers.Control.CacheWrite.urlMap[tile.url],
|
||||
canvasContext.canvas.toDataURL(this.imageFormat)
|
||||
);
|
||||
delete OpenLayers.Control.CacheWrite.urlMap[tile.url];
|
||||
}
|
||||
} catch(e) {
|
||||
// local storage full or CORS violation
|
||||
var reason = e.name || e.message;
|
||||
if (reason && this.quotaRegEx.test(reason)) {
|
||||
this.events.triggerEvent("cachefull", {tile: tile});
|
||||
} else {
|
||||
OpenLayers.Console.error(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: destroy
|
||||
* The destroy method is used to perform any clean up before the control
|
||||
* is dereferenced. Typically this is where event listeners are removed
|
||||
* to prevent memory leaks.
|
||||
*/
|
||||
destroy: function() {
|
||||
if (this.layers || this.map) {
|
||||
var i, layers = this.layers || this.map.layers;
|
||||
for (i=layers.length-1; i>=0; --i) {
|
||||
this.removeLayer({layer: layers[i]});
|
||||
}
|
||||
}
|
||||
if (this.map) {
|
||||
this.map.events.un({
|
||||
addlayer: this.addLayer,
|
||||
removeLayer: this.removeLayer,
|
||||
scope: this
|
||||
});
|
||||
}
|
||||
OpenLayers.Control.prototype.destroy.apply(this, arguments);
|
||||
},
|
||||
|
||||
CLASS_NAME: "OpenLayers.Control.CacheWrite"
|
||||
});
|
||||
|
||||
/**
|
||||
* APIFunction: OpenLayers.Control.CacheWrite.clearCache
|
||||
* Clears all tiles cached with <OpenLayers.Control.CacheWrite> from the cache.
|
||||
*/
|
||||
OpenLayers.Control.CacheWrite.clearCache = function() {
|
||||
if (!window.localStorage) { return; }
|
||||
var i, key;
|
||||
for (i=window.localStorage.length-1; i>=0; --i) {
|
||||
key = window.localStorage.key(i);
|
||||
if (key.substr(0, 8) === "olCache_") {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Property: OpenLayers.Control.CacheWrite.urlMap
|
||||
* {Object} Mapping of same origin urls to cache url keys. Entries will be
|
||||
* deleted as soon as a tile was cached.
|
||||
*/
|
||||
OpenLayers.Control.CacheWrite.urlMap = {};
|
||||
|
||||
|
||||
@@ -65,6 +65,47 @@ OpenLayers.Request = {
|
||||
*/
|
||||
events: new OpenLayers.Events(this),
|
||||
|
||||
/**
|
||||
* Method: makeSameOrigin
|
||||
* Using the specified proxy, returns a same origin url of the provided url.
|
||||
*
|
||||
* Parameters:
|
||||
* url - {String} An arbitrary url
|
||||
* proxy {String|Function} The proxy to use to make the provided url a
|
||||
* same origin url.
|
||||
*
|
||||
* Returns
|
||||
* {String} the same origin url. If no proxy is provided, the returned url
|
||||
* will be the same as the provided url.
|
||||
*/
|
||||
makeSameOrigin: function(url, proxy) {
|
||||
var sameOrigin = !(url.indexOf("http") == 0);
|
||||
var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
|
||||
if (urlParts) {
|
||||
var location = window.location;
|
||||
sameOrigin =
|
||||
urlParts[1] == location.protocol &&
|
||||
urlParts[3] == location.hostname;
|
||||
var uPort = urlParts[4], lPort = location.port;
|
||||
if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
|
||||
sameOrigin = sameOrigin && uPort == lPort;
|
||||
}
|
||||
}
|
||||
if (!sameOrigin) {
|
||||
if (proxy) {
|
||||
if (typeof proxy == "function") {
|
||||
url = proxy(url);
|
||||
} else {
|
||||
url = proxy + encodeURIComponent(url);
|
||||
}
|
||||
} else {
|
||||
OpenLayers.Console.warn(
|
||||
OpenLayers.i18n("proxyNeeded"), {url: url});
|
||||
}
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
||||
/**
|
||||
* APIMethod: issue
|
||||
* Create a new XMLHttpRequest object, open it, set any headers, bind
|
||||
@@ -153,30 +194,7 @@ OpenLayers.Request = {
|
||||
var request = new OpenLayers.Request.XMLHttpRequest();
|
||||
var url = OpenLayers.Util.urlAppend(config.url,
|
||||
OpenLayers.Util.getParameterString(config.params || {}));
|
||||
var sameOrigin = !(url.indexOf("http") == 0);
|
||||
var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
|
||||
if (urlParts) {
|
||||
var location = window.location;
|
||||
sameOrigin =
|
||||
urlParts[1] == location.protocol &&
|
||||
urlParts[3] == location.hostname;
|
||||
var uPort = urlParts[4], lPort = location.port;
|
||||
if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
|
||||
sameOrigin = sameOrigin && uPort == lPort;
|
||||
}
|
||||
}
|
||||
if (!sameOrigin) {
|
||||
if (config.proxy) {
|
||||
if (typeof config.proxy == "function") {
|
||||
url = config.proxy(url);
|
||||
} else {
|
||||
url = config.proxy + encodeURIComponent(url);
|
||||
}
|
||||
} else {
|
||||
OpenLayers.Console.warn(
|
||||
OpenLayers.i18n("proxyNeeded"), {url: url});
|
||||
}
|
||||
}
|
||||
url = OpenLayers.Request.makeSameOrigin(url, config.proxy);
|
||||
request.open(
|
||||
config.method, url, config.async, config.user, config.password
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user