diff --git a/examples/zoomify.html b/examples/zoomify.html
new file mode 100644
index 0000000000..396a736392
--- /dev/null
+++ b/examples/zoomify.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+ Zoomify example
+
+
+
+
+
+
+
+
+
+
+
+
+
Zoomify example
+
Example of a Zoomify source.
+
+
zoomify
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/zoomify.js b/examples/zoomify.js
new file mode 100644
index 0000000000..4c0e3881ee
--- /dev/null
+++ b/examples/zoomify.js
@@ -0,0 +1,52 @@
+goog.require('ol.Map');
+goog.require('ol.RendererHints');
+goog.require('ol.View2D');
+goog.require('ol.layer.Tile');
+goog.require('ol.proj');
+goog.require('ol.proj.Projection');
+goog.require('ol.proj.Units');
+goog.require('ol.source.Zoomify');
+
+// This server does not support CORS, and so is incompatible with WebGL.
+//var imgWidth = 8001;
+//var imgHeight = 6943;
+//var url = 'http://mapy.mzk.cz/AA22/0103/';
+//var crossOrigin = undefined;
+
+var imgWidth = 9911;
+var imgHeight = 6100;
+var url = 'http://vips.vtech.fr/cgi-bin/iipsrv.fcgi?zoomify=' +
+ '/mnt/MD1/AD00/plan_CHU-4HD-01/FOND.TIF/';
+var crossOrigin = 'anonymous';
+
+var imgCenter = [imgWidth / 2, - imgHeight / 2];
+
+// Maps always need a projection, but Zoomify layers are not geo-referenced, and
+// are only measured in pixels. So, we create a fake projection that the map
+// can use to properly display the layer.
+var proj = new ol.proj.Projection({
+ code: 'ZOOMIFY',
+ units: ol.proj.Units.PIXELS,
+ extent: [0, 0, imgWidth, imgHeight]
+});
+
+var source = new ol.source.Zoomify({
+ url: url,
+ size: [imgWidth, imgHeight],
+ crossOrigin: crossOrigin
+});
+
+var map = new ol.Map({
+ layers: [
+ new ol.layer.Tile({
+ source: source
+ })
+ ],
+ renderers: ol.RendererHints.createFromQueryData(),
+ target: 'map',
+ view: new ol.View2D({
+ projection: proj,
+ center: imgCenter,
+ zoom: 0
+ })
+});
diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc
index 3f5b0319f8..bc5d5bf4c4 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -758,6 +758,17 @@
* @todo stability experimental
*/
+/**
+ * @typedef {Object} olx.source.ZoomifyOptions
+ * @property {Array.|undefined} attributions Attributions.
+ * @property {null|string|undefined} crossOrigin Cross origin setting for image
+ * requests.
+ * @property {string|undefined} logo Logo.
+ * @property {!string} url Prefix of URL template.
+ * @property {ol.Size} size Size of the image.
+ * @todo stability experimental
+ */
+
/**
* @typedef {Object} olx.style.CircleOptions
* @property {ol.style.Fill|undefined} fill Fill style.
@@ -841,3 +852,9 @@
* @property {number} maxZoom Maximum zoom.
* @todo stability experimental
*/
+
+/**
+ * @typedef {Object} olx.tilegrid.ZoomifyOptions
+ * @property {!Array.} resolutions Resolutions.
+ * @todo stability experimental
+ */
diff --git a/src/ol/map.js b/src/ol/map.js
index 02c53fa5d5..83f1c9bb66 100644
--- a/src/ol/map.js
+++ b/src/ol/map.js
@@ -1203,7 +1203,7 @@ ol.Map.prototype.updateSize = function() {
if (goog.isNull(targetElement)) {
this.setSize(undefined);
} else {
- var size = goog.style.getSize(targetElement);
+ var size = goog.style.getContentBoxSize(targetElement);
this.setSize([size.width, size.height]);
}
};
diff --git a/src/ol/observable.exports b/src/ol/observable.exports
index 1d6e1ffcdf..1721abd5bd 100644
--- a/src/ol/observable.exports
+++ b/src/ol/observable.exports
@@ -1,3 +1,4 @@
+@exportSymbol ol.Observable
@exportProperty ol.Observable.prototype.on
@exportProperty ol.Observable.prototype.once
@exportProperty ol.Observable.prototype.un
diff --git a/src/ol/proj.exports b/src/ol/proj.exports
index 79616aca0a..c415264d69 100644
--- a/src/ol/proj.exports
+++ b/src/ol/proj.exports
@@ -7,6 +7,7 @@
@exportProperty ol.proj.Units.DEGREES
@exportProperty ol.proj.Units.FEET
@exportProperty ol.proj.Units.METERS
+@exportProperty ol.proj.Units.PIXELS
@exportSymbol ol.proj.addProjection
@exportSymbol ol.proj.get
diff --git a/src/ol/proj/proj.js b/src/ol/proj/proj.js
index bcb3f31e2d..c0c750a88f 100644
--- a/src/ol/proj/proj.js
+++ b/src/ol/proj/proj.js
@@ -40,7 +40,8 @@ ol.proj.ProjectionLike;
ol.proj.Units = {
DEGREES: 'degrees',
FEET: 'ft',
- METERS: 'm'
+ METERS: 'm',
+ PIXELS: 'pixels'
};
diff --git a/src/ol/source/tileimagesource.js b/src/ol/source/tileimagesource.js
index dd9da53a21..bf7d0982db 100644
--- a/src/ol/source/tileimagesource.js
+++ b/src/ol/source/tileimagesource.js
@@ -22,6 +22,9 @@ goog.require('ol.tilegrid.TileGrid');
* logo: (string|undefined),
* opaque: (boolean|undefined),
* projection: ol.proj.ProjectionLike,
+ * tileClass: (function(new: ol.ImageTile, ol.TileCoord,
+ * ol.TileState, string, ?string,
+ * ol.TileLoadFunctionType)|undefined),
* tileGrid: (ol.tilegrid.TileGrid|undefined),
* tileLoadFunction: (ol.TileLoadFunctionType|undefined),
* tileUrlFunction: (ol.TileUrlFunctionType|undefined)}}
@@ -57,25 +60,33 @@ ol.source.TileImage = function(options) {
ol.TileUrlFunction.nullTileUrlFunction;
/**
- * @private
+ * @protected
* @type {?string}
*/
- this.crossOrigin_ =
+ this.crossOrigin =
goog.isDef(options.crossOrigin) ? options.crossOrigin : null;
/**
- * @private
+ * @protected
* @type {ol.TileCache}
*/
- this.tileCache_ = new ol.TileCache();
+ this.tileCache = new ol.TileCache();
/**
- * @private
+ * @protected
* @type {ol.TileLoadFunctionType}
*/
- this.tileLoadFunction_ = goog.isDef(options.tileLoadFunction) ?
+ this.tileLoadFunction = goog.isDef(options.tileLoadFunction) ?
options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction;
+ /**
+ * @protected
+ * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string,
+ * ?string, ol.TileLoadFunctionType)}
+ */
+ this.tileClass = goog.isDef(options.tileClass) ?
+ options.tileClass : ol.ImageTile;
+
};
goog.inherits(ol.source.TileImage, ol.source.Tile);
@@ -93,7 +104,7 @@ ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) {
* @inheritDoc
*/
ol.source.TileImage.prototype.canExpireCache = function() {
- return this.tileCache_.canExpireCache();
+ return this.tileCache.canExpireCache();
};
@@ -101,7 +112,7 @@ ol.source.TileImage.prototype.canExpireCache = function() {
* @inheritDoc
*/
ol.source.TileImage.prototype.expireCache = function(usedTiles) {
- this.tileCache_.expireCache(usedTiles);
+ this.tileCache.expireCache(usedTiles);
};
@@ -110,19 +121,19 @@ ol.source.TileImage.prototype.expireCache = function(usedTiles) {
*/
ol.source.TileImage.prototype.getTile = function(z, x, y, projection) {
var tileCoordKey = this.getKeyZXY(z, x, y);
- if (this.tileCache_.containsKey(tileCoordKey)) {
- return /** @type {!ol.Tile} */ (this.tileCache_.get(tileCoordKey));
+ if (this.tileCache.containsKey(tileCoordKey)) {
+ return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
} else {
goog.asserts.assert(projection);
var tileCoord = new ol.TileCoord(z, x, y);
var tileUrl = this.tileUrlFunction(tileCoord, projection);
- var tile = new ol.ImageTile(
+ var tile = new this.tileClass(
tileCoord,
goog.isDef(tileUrl) ? ol.TileState.IDLE : ol.TileState.EMPTY,
goog.isDef(tileUrl) ? tileUrl : '',
- this.crossOrigin_,
- this.tileLoadFunction_);
- this.tileCache_.set(tileCoordKey, tile);
+ this.crossOrigin,
+ this.tileLoadFunction);
+ this.tileCache.set(tileCoordKey, tile);
return tile;
}
};
@@ -135,7 +146,7 @@ ol.source.TileImage.prototype.setTileUrlFunction = function(tileUrlFunction) {
// FIXME It should be possible to be more intelligent and avoid clearing the
// FIXME cache. The tile URL function would need to be incorporated into the
// FIXME cache key somehow.
- this.tileCache_.clear();
+ this.tileCache.clear();
this.tileUrlFunction = tileUrlFunction;
this.dispatchChangeEvent();
};
@@ -146,7 +157,7 @@ ol.source.TileImage.prototype.setTileUrlFunction = function(tileUrlFunction) {
*/
ol.source.TileImage.prototype.useTile = function(z, x, y) {
var tileCoordKey = this.getKeyZXY(z, x, y);
- if (this.tileCache_.containsKey(tileCoordKey)) {
- this.tileCache_.get(tileCoordKey);
+ if (this.tileCache.containsKey(tileCoordKey)) {
+ this.tileCache.get(tileCoordKey);
}
};
diff --git a/src/ol/source/zoomifysource.exports b/src/ol/source/zoomifysource.exports
new file mode 100644
index 0000000000..38f7c1657b
--- /dev/null
+++ b/src/ol/source/zoomifysource.exports
@@ -0,0 +1 @@
+@exportSymbol ol.source.Zoomify
diff --git a/src/ol/source/zoomifysource.js b/src/ol/source/zoomifysource.js
new file mode 100644
index 0000000000..7e1a372d47
--- /dev/null
+++ b/src/ol/source/zoomifysource.js
@@ -0,0 +1,149 @@
+goog.provide('ol.source.Zoomify');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('ol.ImageTile');
+goog.require('ol.TileCoord');
+goog.require('ol.TileState');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.proj');
+goog.require('ol.source.TileImage');
+goog.require('ol.tilegrid.Zoomify');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.source.TileImage}
+ * @param {olx.source.ZoomifyOptions=} opt_options Options.
+ * @todo stability experimental
+ */
+ol.source.Zoomify = function(opt_options) {
+
+ var options = goog.isDef(opt_options) ? opt_options : {};
+
+ var size = options.size;
+ var imageWidth = size[0];
+ var imageHeight = size[1];
+
+ var tierSizeInTiles = [];
+ var tileSize = ol.DEFAULT_TILE_SIZE;
+ while (imageWidth > tileSize || imageHeight > tileSize) {
+ tierSizeInTiles.push([
+ Math.ceil(imageWidth / tileSize),
+ Math.ceil(imageHeight / tileSize)
+ ]);
+ tileSize += tileSize;
+ }
+ tierSizeInTiles.push([1, 1]);
+ tierSizeInTiles.reverse();
+
+ var resolutions = [1];
+ var tileCountUpToTier = [0];
+ var i, ii;
+ for (i = 1, ii = tierSizeInTiles.length; i < ii; i++) {
+ resolutions.push(1 << i);
+ tileCountUpToTier.push(
+ tierSizeInTiles[i - 1][0] * tierSizeInTiles[i - 1][1] +
+ tileCountUpToTier[i - 1]
+ );
+ }
+ resolutions.reverse();
+
+ var tileGrid = new ol.tilegrid.Zoomify({
+ resolutions: resolutions
+ });
+
+ var url = options.url;
+ var tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
+ tileGrid.createTileCoordTransform({extent: [0, 0, size[0], size[1]]}),
+ /**
+ * @this {ol.source.TileImage}
+ * @param {ol.TileCoord} tileCoord Tile Coordinate.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {string|undefined} Tile URL.
+ */
+ function(tileCoord, projection) {
+ if (goog.isNull(tileCoord)) {
+ return undefined;
+ } else {
+ var tileIndex = tileCoord.x +
+ tileCoord.y * tierSizeInTiles[tileCoord.z][0] +
+ tileCountUpToTier[tileCoord.z];
+ var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0;
+ return url + 'TileGroup' + tileGroup + '/' +
+ tileCoord.z + '-' + tileCoord.x + '-' + tileCoord.y + '.jpg';
+ }
+ });
+
+ goog.base(this, {
+ attributions: options.attributions,
+ crossOrigin: options.crossOrigin,
+ logo: options.logo,
+ tileClass: ol.source.ZoomifyTile_,
+ tileGrid: tileGrid,
+ tileUrlFunction: tileUrlFunction
+ });
+
+};
+goog.inherits(ol.source.Zoomify, ol.source.TileImage);
+
+
+
+/**
+ * @constructor
+ * @extends {ol.ImageTile}
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.TileState} state State.
+ * @param {string} src Image source URI.
+ * @param {?string} crossOrigin Cross origin.
+ * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
+ * @private
+ */
+ol.source.ZoomifyTile_ = function(
+ tileCoord, state, src, crossOrigin, tileLoadFunction) {
+
+ goog.base(this, tileCoord, state, src, crossOrigin, tileLoadFunction);
+
+ /**
+ * @private
+ * @type {Object.}
+ */
+ this.zoomifyImageByContext_ = {};
+
+};
+goog.inherits(ol.source.ZoomifyTile_, ol.ImageTile);
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.ZoomifyTile_.prototype.getImage = function(opt_context) {
+ var key = goog.isDef(opt_context) ? goog.getUid(opt_context).toString() : '';
+ if (key in this.zoomifyImageByContext_) {
+ return this.zoomifyImageByContext_[key];
+ } else {
+ var image = goog.base(this, 'getImage', opt_context);
+ if (this.state == ol.TileState.LOADED) {
+ if (image.width == ol.DEFAULT_TILE_SIZE &&
+ image.height == ol.DEFAULT_TILE_SIZE) {
+ this.zoomifyImageByContext_[key] = image;
+ return image;
+ } else {
+ var canvas = /** @type {HTMLCanvasElement} */
+ (goog.dom.createElement(goog.dom.TagName.CANVAS));
+ canvas.width = ol.DEFAULT_TILE_SIZE;
+ canvas.height = ol.DEFAULT_TILE_SIZE;
+ var context = /** @type {CanvasRenderingContext2D} */
+ (canvas.getContext('2d'));
+ context.drawImage(image, 0, 0);
+ this.zoomifyImageByContext_[key] = canvas;
+ return canvas;
+ }
+ } else {
+ return image;
+ }
+ }
+};
diff --git a/src/ol/tilegrid/zoomifytilegrid.exports b/src/ol/tilegrid/zoomifytilegrid.exports
new file mode 100644
index 0000000000..afbfc424a2
--- /dev/null
+++ b/src/ol/tilegrid/zoomifytilegrid.exports
@@ -0,0 +1 @@
+@exportSymbol ol.tilegrid.Zoomify
diff --git a/src/ol/tilegrid/zoomifytilegrid.js b/src/ol/tilegrid/zoomifytilegrid.js
new file mode 100644
index 0000000000..115f1734c8
--- /dev/null
+++ b/src/ol/tilegrid/zoomifytilegrid.js
@@ -0,0 +1,86 @@
+goog.provide('ol.tilegrid.Zoomify');
+
+goog.require('goog.math');
+goog.require('ol.TileCoord');
+goog.require('ol.proj');
+goog.require('ol.tilegrid.TileGrid');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.tilegrid.TileGrid}
+ * @param {olx.tilegrid.ZoomifyOptions=} opt_options Options.
+ * @todo stability experimental
+ */
+ol.tilegrid.Zoomify = function(opt_options) {
+ var options = goog.isDef(opt_options) ? opt_options : options;
+ goog.base(this, {
+ origin: [0, 0],
+ resolutions: options.resolutions
+ });
+
+};
+goog.inherits(ol.tilegrid.Zoomify, ol.tilegrid.TileGrid);
+
+
+/**
+ * @inheritDoc
+ */
+ol.tilegrid.Zoomify.prototype.createTileCoordTransform = function(opt_options) {
+ var options = goog.isDef(opt_options) ? opt_options : {};
+ var minZ = this.minZoom;
+ var maxZ = this.maxZoom;
+ var tmpTileCoord = new ol.TileCoord(0, 0, 0);
+ /** @type {Array.} */
+ var tileRangeByZ = null;
+ if (goog.isDef(options.extent)) {
+ tileRangeByZ = new Array(maxZ + 1);
+ var z;
+ for (z = 0; z <= maxZ; ++z) {
+ if (z < minZ) {
+ tileRangeByZ[z] = null;
+ } else {
+ tileRangeByZ[z] = this.getTileRangeForExtentAndZ(options.extent, z);
+ }
+ }
+ }
+ return (
+ /**
+ * @param {ol.TileCoord} tileCoord Tile coordinate.
+ * @param {ol.proj.Projection} projection Projection.
+ * @param {ol.TileCoord=} opt_tileCoord Destination tile coordinate.
+ * @return {ol.TileCoord} Tile coordinate.
+ */
+ function(tileCoord, projection, opt_tileCoord) {
+ var z = tileCoord.z;
+ if (z < minZ || maxZ < z) {
+ return null;
+ }
+ var n = Math.pow(2, z);
+ var x = tileCoord.x;
+ if (x < 0 || n <= x) {
+ return null;
+ }
+ var y = tileCoord.y;
+ if (y < -n || -1 < y) {
+ return null;
+ }
+ if (!goog.isNull(tileRangeByZ)) {
+ tmpTileCoord.z = z;
+ tmpTileCoord.x = x;
+ tmpTileCoord.y = -y - 1;
+ if (!tileRangeByZ[z].contains(tmpTileCoord)) {
+ return null;
+ }
+ }
+ if (goog.isDef(opt_tileCoord)) {
+ opt_tileCoord.z = z;
+ opt_tileCoord.x = x;
+ opt_tileCoord.y = -y - 1;
+ return opt_tileCoord;
+ } else {
+ return new ol.TileCoord(z, x, -y - 1);
+ }
+ });
+};