Adding CacheRead and CacheWrite controls.
These controls read from and write to the browser's offline storage. Example with a seeding tool included.
This commit is contained in:
43
examples/offline-storage.html
Normal file
43
examples/offline-storage.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<title>OpenLayers Offline Storage Example</title>
|
||||||
|
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
|
||||||
|
<link rel="stylesheet" href="style.css" type="text/css">
|
||||||
|
<style type="text/css">
|
||||||
|
.olControlAttribution {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="../lib/OpenLayers.js"></script>
|
||||||
|
<script src="offline-storage.js"></script>
|
||||||
|
</head>
|
||||||
|
<body onload="init()">
|
||||||
|
<h1 id="title">Offline Storage Example</h1>
|
||||||
|
|
||||||
|
<div id="tags">
|
||||||
|
local storage, persistence, cache, html5
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="shortdesc">Caching viewed tiles</div>
|
||||||
|
|
||||||
|
<div id="map" class="smallmap"></div>
|
||||||
|
<div>Cache status: <span id="hits"></span> <span id="status"></span></div>
|
||||||
|
<div><input id="read" type="checkbox">Read from cache [<input id="tileloadstart" name="type" type="radio">try cache first] [<input id="tileerror" name="type" type="radio">try online first<sup>1</sup>]</div>
|
||||||
|
<div><input id="write" type="checkbox">Write to cache</div>
|
||||||
|
<div><button id="clear">Clear cached tiles</button><button id="seed">Seed current extent</button>
|
||||||
|
<br>
|
||||||
|
<p><sup>1</sup> <small>Disconnect your device from the network to test - only works for same origin layers.</small></p>
|
||||||
|
<br>
|
||||||
|
<div id="docs">
|
||||||
|
<p>This example shows how to use the CacheWrite control to cache tiles
|
||||||
|
that are being viewed in the browser's local storage, and how to use
|
||||||
|
the CacheRead control to use cached tiles when offline or on a slow
|
||||||
|
connection. See <a href="offline-storage.js">offline-storage.js</a>
|
||||||
|
for the source code.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
183
examples/offline-storage.js
Normal file
183
examples/offline-storage.js
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
var map, cacheWrite, cacheRead1, cacheRead2;
|
||||||
|
function init(){
|
||||||
|
map = new OpenLayers.Map({
|
||||||
|
div: "map",
|
||||||
|
projection: "EPSG:900913",
|
||||||
|
layers: [new OpenLayers.Layer.OSM("OpenStreetMap (CORS)", null, {
|
||||||
|
eventListeners: {
|
||||||
|
loadend: updateLayerInfo,
|
||||||
|
tileloaded: updateTileInfo,
|
||||||
|
tileerror: updateTileInfo
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new OpenLayers.Layer.WMS("OSGeo (same origin - proxied)", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
|
||||||
|
layers: "basic"
|
||||||
|
}, {
|
||||||
|
eventListeners: {
|
||||||
|
tileloadstart: function(evt) {
|
||||||
|
// send requests through proxy
|
||||||
|
evt.tile.url = "proxy.cgi?url=" + encodeURIComponent(evt.tile.url);
|
||||||
|
},
|
||||||
|
loadend: updateLayerInfo,
|
||||||
|
tileloaded: updateTileInfo
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
center: [0,0],
|
||||||
|
zoom: 1
|
||||||
|
});
|
||||||
|
cacheWrite = new OpenLayers.Control.CacheWrite({
|
||||||
|
imageFormat: "image/jpeg",
|
||||||
|
eventListeners: {
|
||||||
|
cachefull: function() {
|
||||||
|
status.innerHTML = "Cache full.";
|
||||||
|
cacheFull = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// try cache before loading from remote resource
|
||||||
|
cacheRead1 = new OpenLayers.Control.CacheRead({
|
||||||
|
eventListeners: {
|
||||||
|
activate: function() {
|
||||||
|
cacheRead2.deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// try loading from remote resource and fall back to cache
|
||||||
|
cacheRead2 = new OpenLayers.Control.CacheRead({
|
||||||
|
autoActivate: false,
|
||||||
|
fetchEvent: "tileerror",
|
||||||
|
eventListeners: {
|
||||||
|
activate: function() {
|
||||||
|
cacheRead1.deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var layerSwitcher = new OpenLayers.Control.LayerSwitcher();
|
||||||
|
map.addControls([cacheWrite, cacheRead1, cacheRead2, layerSwitcher]);
|
||||||
|
layerSwitcher.maximizeControl();
|
||||||
|
|
||||||
|
// add UI and behavior
|
||||||
|
var status = document.getElementById("status"),
|
||||||
|
hits = document.getElementById("hits"),
|
||||||
|
previousCount = -1,
|
||||||
|
cacheHits = 0,
|
||||||
|
cacheFull = false;
|
||||||
|
updateLayerInfo();
|
||||||
|
var read = document.getElementById("read");
|
||||||
|
read.checked = true;
|
||||||
|
read.onclick = toggleRead;
|
||||||
|
var write = document.getElementById("write");
|
||||||
|
write.checked = false;
|
||||||
|
write.onclick = toggleWrite;
|
||||||
|
document.getElementById("clear").onclick = clearCache;
|
||||||
|
var tileloadstart = document.getElementById("tileloadstart");
|
||||||
|
tileloadstart.checked = "checked";
|
||||||
|
tileloadstart.onclick = setType;
|
||||||
|
document.getElementById("tileerror").onclick = setType;
|
||||||
|
document.getElementById("seed").onclick = seedCache;
|
||||||
|
|
||||||
|
// update the number of cached tiles and detect local storage support
|
||||||
|
function updateLayerInfo(evt) {
|
||||||
|
if (window.localStorage) {
|
||||||
|
if (previousCount !== localStorage.length) {
|
||||||
|
status.innerHTML = localStorage.length + " entries in cache.";
|
||||||
|
}
|
||||||
|
previousCount = localStorage.length;
|
||||||
|
} else {
|
||||||
|
status.innerHTML = "Local storage not supported. Try a different browser.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update the number of cache hits and detect missing CORS support
|
||||||
|
function updateTileInfo(evt) {
|
||||||
|
if (cacheWrite.active) {
|
||||||
|
try {
|
||||||
|
var canvasContext = evt.tile.getCanvasContext();
|
||||||
|
if (canvasContext) {
|
||||||
|
// will throw an exception if CORS image requests are not supported
|
||||||
|
canvasContext.canvas.toDataURL();
|
||||||
|
} else {
|
||||||
|
status.innerHTML = "Canvas not supported. Try a different browser.";
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
status.innerHTML = "CORS image requests not supported. Try a different layer.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (evt.tile.url.substr(0, 5) === "data:") {
|
||||||
|
cacheHits++;
|
||||||
|
}
|
||||||
|
hits.innerHTML = cacheHits + " cache hits.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn the cacheRead controls on and off
|
||||||
|
function toggleRead() {
|
||||||
|
if (!this.checked) {
|
||||||
|
cacheRead1.deactivate();
|
||||||
|
cacheRead2.deactivate();
|
||||||
|
} else {
|
||||||
|
setType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn the cacheWrite control on and off
|
||||||
|
function toggleWrite() {
|
||||||
|
cacheWrite[cacheWrite.active ? "deactivate" : "activate"]();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear all tiles from the cache
|
||||||
|
function clearCache() {
|
||||||
|
OpenLayers.Control.CacheWrite.clearCache();
|
||||||
|
cacheFull = false;
|
||||||
|
updateLayerInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// activate the cacheRead control that matches the desired fetch strategy
|
||||||
|
function setType() {
|
||||||
|
if (tileloadstart.checked) {
|
||||||
|
cacheRead1.activate();
|
||||||
|
} else {
|
||||||
|
cacheRead2.activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// seed the cache
|
||||||
|
function seedCache() {
|
||||||
|
var zoom = map.getZoom();
|
||||||
|
var extent = map.getExtent();
|
||||||
|
var center = map.getCenter();
|
||||||
|
var active = cacheWrite.active;
|
||||||
|
var tileWidth = map.baseLayer.tileSize.w;
|
||||||
|
var layer = map.baseLayer;
|
||||||
|
var buffer = layer.buffer;
|
||||||
|
// make sure the next setCenter triggers a load
|
||||||
|
map.zoomTo(zoom === layer.numZoomLevels-1 ? zoom - 1 : zoom + 1);
|
||||||
|
// turn on cache writing
|
||||||
|
cacheWrite.activate();
|
||||||
|
// turn off cache reading
|
||||||
|
cacheRead1.deactivate();
|
||||||
|
cacheRead2.deactivate();
|
||||||
|
|
||||||
|
layer.events.register("loadend", this, function next() {
|
||||||
|
var nextZoom = map.getZoom() + 1;
|
||||||
|
var extentWidth = extent.getWidth() / map.getResolutionForZoom(nextZoom);
|
||||||
|
// adjust the layer's buffer size so we don't have to pan
|
||||||
|
layer.buffer = Math.ceil((extentWidth / tileWidth - map.getSize().w / tileWidth) / 2);
|
||||||
|
map.zoomIn();
|
||||||
|
if (cacheFull || nextZoom === layer.numZoomLevels-1) {
|
||||||
|
// we're done - restore previous settings
|
||||||
|
layer.events.unregister("loadend", this, next);
|
||||||
|
layer.buffer = buffer;
|
||||||
|
map.setCenter(center, zoom);
|
||||||
|
if (!active) {
|
||||||
|
cacheWrite.deactivate();
|
||||||
|
}
|
||||||
|
if (read.checked) {
|
||||||
|
setType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// start seeding
|
||||||
|
map.setCenter(center, zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -209,6 +209,8 @@
|
|||||||
"OpenLayers/Control.js",
|
"OpenLayers/Control.js",
|
||||||
"OpenLayers/Control/Attribution.js",
|
"OpenLayers/Control/Attribution.js",
|
||||||
"OpenLayers/Control/Button.js",
|
"OpenLayers/Control/Button.js",
|
||||||
|
"OpenLayers/Control/CacheRead.js",
|
||||||
|
"OpenLayers/Control/CacheWrite.js",
|
||||||
"OpenLayers/Control/ZoomBox.js",
|
"OpenLayers/Control/ZoomBox.js",
|
||||||
"OpenLayers/Control/ZoomToMaxExtent.js",
|
"OpenLayers/Control/ZoomToMaxExtent.js",
|
||||||
"OpenLayers/Control/DragPan.js",
|
"OpenLayers/Control/DragPan.js",
|
||||||
|
|||||||
149
lib/OpenLayers/Control/CacheRead.js
Normal file
149
lib/OpenLayers/Control/CacheRead.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/* 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.HTTPRequest>)}. 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 || this.map.layers;
|
||||||
|
for (i=layers.length-1; i>=0; --i) {
|
||||||
|
this.addLayer({layer: layers[i]});
|
||||||
|
}
|
||||||
|
if (!this.layers) {
|
||||||
|
this.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) {
|
||||||
|
var tile = evt.tile,
|
||||||
|
dataURI = window.localStorage.getItem("olCache_" + tile.url);
|
||||||
|
if (dataURI) {
|
||||||
|
if (evt.type === "tileerror") {
|
||||||
|
tile.setImgSrc(dataURI);
|
||||||
|
} else {
|
||||||
|
tile.url = 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"
|
||||||
|
});
|
||||||
197
lib/OpenLayers/Control/CacheWrite.js
Normal file
197
lib/OpenLayers/Control/CacheWrite.js
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/* 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.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.
|
||||||
|
*
|
||||||
|
* 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 || this.map.layers;
|
||||||
|
for (i=layers.length-1; i>=0; --i) {
|
||||||
|
this.addLayer({layer: layers[i]});
|
||||||
|
}
|
||||||
|
if (!this.layers) {
|
||||||
|
this.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("tileloaded", this, this.cache);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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("tileloaded", this, this.cache);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.url.substr(0, 5) !== 'data:') {
|
||||||
|
try {
|
||||||
|
var canvasContext = tile.getCanvasContext();
|
||||||
|
if (canvasContext) {
|
||||||
|
window.localStorage.setItem(
|
||||||
|
"olCache_" + tile.url,
|
||||||
|
canvasContext.canvas.toDataURL(this.imageFormat)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} 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 {
|
||||||
|
// throw exception in the next cycle
|
||||||
|
window.setTimeout(function() { throw(e); }, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
98
tests/Control/CacheRead.html
Normal file
98
tests/Control/CacheRead.html
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Because browsers that implement requestAnimationFrame may not execute
|
||||||
|
* animation functions while a window is not displayed (e.g. in a hidden
|
||||||
|
* iframe as in these tests), we mask the native implementations here. The
|
||||||
|
* native requestAnimationFrame functionality is tested in Util.html and
|
||||||
|
* in PanZoom.html (where a popup is opened before panning). The tests
|
||||||
|
* here will test the fallback setTimeout implementation for animation.
|
||||||
|
*/
|
||||||
|
window.requestAnimationFrame =
|
||||||
|
window.webkitRequestAnimationFrame =
|
||||||
|
window.mozRequestAnimationFrame =
|
||||||
|
window.oRequestAnimationFrame =
|
||||||
|
window.msRequestAnimationFrame = null;
|
||||||
|
</script>
|
||||||
|
<script src="../OLLoader.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function test_addLayer_removeLayer(t) {
|
||||||
|
t.plan(6);
|
||||||
|
var control = new OpenLayers.Control.CacheRead();
|
||||||
|
var map = new OpenLayers.Map({
|
||||||
|
div: "map",
|
||||||
|
controls: [control],
|
||||||
|
layers: [
|
||||||
|
new OpenLayers.Layer.WMS("One"),
|
||||||
|
new OpenLayers.Layer.WMS("Two")
|
||||||
|
]
|
||||||
|
});
|
||||||
|
t.ok(map.layers[0].events.listeners.tileloadstart, "tileloadstart listener registered on layer One");
|
||||||
|
t.ok(map.layers[1].events.listeners.tileloadstart, "tileloadstart listener registered on layer Two");
|
||||||
|
control.destroy();
|
||||||
|
t.ok(!map.layers[1].events.listeners.tileloadstart.length, "tileloadstart listener unregistered");
|
||||||
|
|
||||||
|
control = new OpenLayers.Control.CacheRead({
|
||||||
|
fetchEvent: "tileerror",
|
||||||
|
layers: [map.layers[0]]
|
||||||
|
});
|
||||||
|
map.addControl(control);
|
||||||
|
t.ok(map.layers[0].events.listeners.tileerror, "tileerror listener registered on layer One");
|
||||||
|
t.ok(!map.layers[1].events.listeners.tileerror, "tileerror listener not registered on layer Two");
|
||||||
|
control.destroy();
|
||||||
|
t.ok(!map.layers[0].events.listeners.tileerror.length, "tileerror listener unregistered");
|
||||||
|
|
||||||
|
map.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_fetch(t) {
|
||||||
|
|
||||||
|
if (!window.localStorage) {
|
||||||
|
t.plan(1);
|
||||||
|
var scope = {active: true};
|
||||||
|
t.eq(OpenLayers.Control.CacheRead.prototype.fetch.call(scope), undefined, "no tiles fetched when localStorage is not supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.plan(4);
|
||||||
|
|
||||||
|
var data = "data:image/gif;base64,R0lGODlhAQABAIAAAP7//wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
|
||||||
|
window.localStorage.setItem("olCache_foo/1/1/1", data);
|
||||||
|
window.localStorage.setItem("olCache_bar/1/1/1", data);
|
||||||
|
|
||||||
|
var layer1 = new OpenLayers.Layer.XYZ("One", "foo/${x}/${y}/${z}");
|
||||||
|
var layer2 = new OpenLayers.Layer.XYZ("Two", "bar/${x}/${y}/${z}", {isBaseLayer: false});
|
||||||
|
var control1 = new OpenLayers.Control.CacheRead({
|
||||||
|
layers: [layer1]
|
||||||
|
});
|
||||||
|
var control2 = new OpenLayers.Control.CacheRead({
|
||||||
|
layers: [layer2],
|
||||||
|
fetchEvent: "tileerror"
|
||||||
|
});
|
||||||
|
var map = new OpenLayers.Map({
|
||||||
|
div: "map",
|
||||||
|
projection: "EPSG:900913",
|
||||||
|
controls: [control1, control2],
|
||||||
|
layers: [layer1, layer2],
|
||||||
|
zoom: 1,
|
||||||
|
center: [0, 0]
|
||||||
|
});
|
||||||
|
t.delay_call(1, function() {
|
||||||
|
t.eq(layer1.grid[1][1].imgDiv.src, data, "[tileloadstart] tile content from cache");
|
||||||
|
t.ok(layer1.grid[0][0].imgDiv.src !== data, "[tileloadstart] tile content from remote resource");
|
||||||
|
t.eq(layer2.grid[1][1].imgDiv.src, data, "[tileerror] tile content from cache");
|
||||||
|
t.ok(layer2.grid[0][0].imgDiv.src !== data, "[tileerror] tile content from remote resource");
|
||||||
|
|
||||||
|
window.localStorage.removeItem("olCache_foo/1/1/1");
|
||||||
|
window.localStorage.removeItem("olCache_bar/1/1/1");
|
||||||
|
map.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map" style="width: 400px; height: 250px;"/>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
87
tests/Control/CacheWrite.html
Normal file
87
tests/Control/CacheWrite.html
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="../OLLoader.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function test_addLayer_removeLayer(t) {
|
||||||
|
t.plan(6);
|
||||||
|
var control = new OpenLayers.Control.CacheWrite();
|
||||||
|
var map = new OpenLayers.Map({
|
||||||
|
div: "map",
|
||||||
|
controls: [control],
|
||||||
|
layers: [
|
||||||
|
new OpenLayers.Layer.WMS("One"),
|
||||||
|
new OpenLayers.Layer.WMS("Two")
|
||||||
|
]
|
||||||
|
});
|
||||||
|
t.ok(map.layers[0].events.listeners.tileloaded, "tileloaded listener registered on layer One");
|
||||||
|
t.ok(map.layers[1].events.listeners.tileloaded, "tileloaded listener registered on layer Two");
|
||||||
|
control.destroy();
|
||||||
|
t.ok(!map.layers[1].events.listeners.tileloaded.length, "tileloaded listener unregistered");
|
||||||
|
|
||||||
|
control = new OpenLayers.Control.CacheWrite({
|
||||||
|
layers: [map.layers[0]]
|
||||||
|
});
|
||||||
|
map.addControl(control);
|
||||||
|
t.ok(map.layers[0].events.listeners.tileloaded.length, "tileloaded listener registered on layer One");
|
||||||
|
t.ok(!map.layers[1].events.listeners.tileloaded.length, "tileloaded listener not registered on layer Two");
|
||||||
|
control.destroy();
|
||||||
|
t.ok(!map.layers[0].events.listeners.tileloaded.length, "tileloaded listener unregistered");
|
||||||
|
|
||||||
|
map.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_cache_clearCache(t) {
|
||||||
|
|
||||||
|
if (!window.localStorage) {
|
||||||
|
t.plan(2);
|
||||||
|
var scope = {active: true};
|
||||||
|
t.eq(OpenLayers.Control.CacheWrite.prototype.cache.call(scope), undefined, "no tiles cached when localStorage is not supported.");
|
||||||
|
t.ok(!OpenLayers.Control.CacheWrite.clearCache(), "clearCache does nothing when localStorage is not supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.plan(3);
|
||||||
|
OpenLayers.Control.CacheWrite.clearCache();
|
||||||
|
var length = window.localStorage.length;
|
||||||
|
|
||||||
|
var tiles = 0;
|
||||||
|
var layer = new OpenLayers.Layer.XYZ("One", "../../img/blank.gif?${x},${y},${z}", {
|
||||||
|
eventListeners: {
|
||||||
|
tileloaded: function() {
|
||||||
|
tiles++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var control = new OpenLayers.Control.CacheWrite({autoActivate: true});
|
||||||
|
var map = new OpenLayers.Map({
|
||||||
|
div: "map",
|
||||||
|
projection: "EPSG:900913",
|
||||||
|
controls: [control],
|
||||||
|
layers: [layer],
|
||||||
|
zoom: 1,
|
||||||
|
center: [0, 0]
|
||||||
|
});
|
||||||
|
t.delay_call(1, function() {
|
||||||
|
var canvasContext = layer.grid[1][1].getCanvasContext();
|
||||||
|
t.eq(window.localStorage.length, length + (canvasContext ? tiles : 0), "cache filled with tiles");
|
||||||
|
var url = layer.grid[1][1].url;
|
||||||
|
// content will be null for browsers that have localStorage but no canvas support
|
||||||
|
var content = canvasContext ? canvasContext.canvas.toDataURL("image/png") : null;
|
||||||
|
t.eq(window.localStorage.getItem("olCache_"+url), content, "localStorage contains correct image data");
|
||||||
|
|
||||||
|
var key = Math.random();
|
||||||
|
window.localStorage.setItem(key, "bar");
|
||||||
|
OpenLayers.Control.CacheWrite.clearCache();
|
||||||
|
t.eq(window.localStorage.length, length + 1, "cache cleared, but foreign entries left in localStorage");
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
|
||||||
|
map.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map" style="width: 400px; height: 250px;"/>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
<li>Control/Attribution.html</li>
|
<li>Control/Attribution.html</li>
|
||||||
<li>Control/ArgParser.html</li>
|
<li>Control/ArgParser.html</li>
|
||||||
<li>Control/Button.html</li>
|
<li>Control/Button.html</li>
|
||||||
|
<li>Control/CacheRead.html</li>
|
||||||
|
<li>Control/CacheWrite.html</li>
|
||||||
<li>Control/DragFeature.html</li>
|
<li>Control/DragFeature.html</li>
|
||||||
<li>Control/DragPan.html</li>
|
<li>Control/DragPan.html</li>
|
||||||
<li>Control/DrawFeature.html</li>
|
<li>Control/DrawFeature.html</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user