Merge pull request #301 from ahocevar/offline

Adding CacheRead and CacheWrite controls. r=@elemoine,@fredj
This commit is contained in:
ahocevar
2012-03-12 08:29:00 -07:00
13 changed files with 1030 additions and 24 deletions

36
examples/cache-read.html Normal file
View File

@@ -0,0 +1,36 @@
<!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 Cache Read Example</title>
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
<link rel="stylesheet" href="style.css" type="text/css">
<script src="../lib/OpenLayers.js"></script>
<script src="cache-read.js"></script>
</head>
<body onload="init()">
<h1 id="title">Cache Read 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 id="status"></div>
<br>
<div id="docs">
<p>This example shows how to use the CacheRead control to fetch cached
tiles from the browser's Local Storage. As you pan and zoom the map,
you can see how the number of cache hits incrases as you browse regions
that are available in the cache.</p>
<p>To fill the cache with tiles, switch to the
<a href="cache-write.html">cache-write.html</a> example.</p>
<p>See <a href="cache-read.js">cache-read.js</a> for the source
code.</p>
</div>
</body>
</html>

36
examples/cache-read.js Normal file
View File

@@ -0,0 +1,36 @@
var map, cacheRead;
function init() {
map = new OpenLayers.Map({
div: "map",
projection: "EPSG:900913",
layers: [
new OpenLayers.Layer.WMS("OSGeo", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
layers: "basic"
}, {
eventListeners: {
tileloaded: updateHits
}
})
],
center: [0, 0],
zoom: 1
});
cacheRead = new OpenLayers.Control.CacheRead();
map.addControl(cacheRead);
// User interface
var status = document.getElementById("status"),
hits = 0;
// update the number of cached tiles and detect local storage support
function updateHits(evt) {
hits += evt.tile.url.substr(0, 5) === "data:";
if (window.localStorage) {
status.innerHTML = hits + " cache hits.";
} else {
status.innerHTML = "Local storage not supported. Try a different browser.";
}
}
}

37
examples/cache-write.html Normal file
View File

@@ -0,0 +1,37 @@
<!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 Cache Write Example</title>
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
<link rel="stylesheet" href="style.css" type="text/css">
<script src="../lib/OpenLayers.js"></script>
<script>OpenLayers.Console = window.console;</script>
<script src="cache-write.js"></script>
</head>
<body onload="init()">
<h1 id="title">Cache Write 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="status"></span></div>
<div><button id="clear">Clear cache</button></div>
<br>
<div id="docs">
<p>This example shows how to use the CacheWrite control to cache the
tiles. Caching is turned on, and as you pan and zoom the map, every
tile that is loaded is also copied to the browsers Local Storage.</p>
<p>To use the cached tiles, switch to the
<a href="cache-read.html">cache-read.html</a> example.</p>
<p>See <a href="cache-write.js">cache-write.js</a> for the source
code.</p>
</div>
</body>
</html>

48
examples/cache-write.js Normal file
View File

@@ -0,0 +1,48 @@
// Use proxy to get same origin URLs for tiles that don't support CORS.
OpenLayers.ProxyHost = "proxy.cgi?url=";
var map, cacheWrite;
function init() {
map = new OpenLayers.Map({
div: "map",
projection: "EPSG:900913",
layers: [
new OpenLayers.Layer.WMS("OSGeo", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
layers: "basic"
}, {
eventListeners: {
tileloaded: updateStatus
}
})
],
center: [0, 0],
zoom: 1
});
cacheWrite = new OpenLayers.Control.CacheWrite({
autoActivate: true,
imageFormat: "image/jpeg",
eventListeners: {
cachefull: function() { status.innerHTML = "Cache full."; }
}
});
map.addControl(cacheWrite);
// User interface
var status = document.getElementById("status");
document.getElementById("clear").onclick = function() {
OpenLayers.Control.CacheWrite.clearCache();
updateStatus();
};
// update the number of cached tiles and detect local storage support
function updateStatus() {
if (window.localStorage) {
status.innerHTML = localStorage.length + " entries in cache.";
} else {
status.innerHTML = "Local storage not supported. Try a different browser.";
}
}
}

View File

@@ -0,0 +1,44 @@
<!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>OpenLayers.Console = window.console;</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>

199
examples/offline-storage.js Normal file
View File

@@ -0,0 +1,199 @@
// Use proxy to get same origin URLs for tiles that don't support CORS.
OpenLayers.ProxyHost = "proxy.cgi?url=";
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: {
tileloaded: updateStatus,
loadend: detect
}
}),
new OpenLayers.Layer.WMS("OSGeo (same origin - proxied)", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
layers: "basic"
}, {
eventListeners: {
tileloaded: updateStatus
}
})
],
center: [0, 0],
zoom: 1
});
// 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();
}
}
});
cacheWrite = new OpenLayers.Control.CacheWrite({
imageFormat: "image/jpeg",
eventListeners: {
cachefull: function() {
if (seeding) {
stopSeeding();
}
status.innerHTML = "Cache full.";
}
}
});
var layerSwitcher = new OpenLayers.Control.LayerSwitcher();
map.addControls([cacheRead1, cacheRead2, cacheWrite, layerSwitcher]);
layerSwitcher.maximizeControl();
// add UI and behavior
var status = document.getElementById("status"),
hits = document.getElementById("hits"),
cacheHits = 0,
seeding = false;
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 = startSeeding;
// detect what the browser supports
function detect(evt) {
// detection is only done once, so we remove the listener.
evt.object.events.unregister("loadend", null, detect);
var tile = map.baseLayer.grid[0][0];
try {
var canvasContext = 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) {
// we remove the OSM layer if CORS image requests are not supported.
map.setBaseLayer(map.layers[1]);
evt.object.destroy();
layerSwitcher.destroy();
}
}
// update the number of cache hits and detect missing CORS support
function updateStatus(evt) {
if (window.localStorage) {
status.innerHTML = localStorage.length + " entries in cache.";
} else {
status.innerHTML = "Local storage not supported. Try a different browser.";
}
if (evt && 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();
updateStatus();
}
// activate the cacheRead control that matches the desired fetch strategy
function setType() {
if (tileloadstart.checked) {
cacheRead1.activate();
} else {
cacheRead2.activate();
}
}
// start seeding the cache
function startSeeding() {
var layer = map.baseLayer,
zoom = map.getZoom();
seeding = {
zoom: zoom,
extent: map.getExtent(),
center: map.getCenter(),
cacheWriteActive: cacheWrite.active,
buffer: layer.buffer,
layer: layer
};
// 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", null, seed);
// start seeding
map.setCenter(seeding.center, zoom);
}
// seed a zoom level based on the extent at the time startSeeding was called
function seed() {
var layer = seeding.layer;
var tileWidth = layer.tileSize.w;
var nextZoom = map.getZoom() + 1;
var extentWidth = seeding.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 (nextZoom === layer.numZoomLevels-1) {
stopSeeding();
}
}
// stop seeding (when done or when cache is full)
function stopSeeding() {
// we're done - restore previous settings
seeding.layer.events.unregister("loadend", null, seed);
seeding.layer.buffer = seeding.buffer;
map.setCenter(seeding.center, seeding.zoom);
if (!seeding.cacheWriteActive) {
cacheWrite.deactivate();
}
if (read.checked) {
setType();
}
seeding = false;
}
}

View File

@@ -211,6 +211,8 @@
"OpenLayers/Control.js",
"OpenLayers/Control/Attribution.js",
"OpenLayers/Control/Button.js",
"OpenLayers/Control/CacheRead.js",
"OpenLayers/Control/CacheWrite.js",
"OpenLayers/Control/ZoomBox.js",
"OpenLayers/Control/ZoomToMaxExtent.js",
"OpenLayers/Control/DragPan.js",

View 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"
});

View 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 = {};

View File

@@ -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
);

View 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>

View 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>

View File

@@ -13,6 +13,8 @@
<li>Control/Attribution.html</li>
<li>Control/ArgParser.html</li>
<li>Control/Button.html</li>
<li>Control/CacheRead.html</li>
<li>Control/CacheWrite.html</li>
<li>Control/DragFeature.html</li>
<li>Control/DragPan.html</li>
<li>Control/DrawFeature.html</li>