From 2b753410684ca6a3b49d1eb439b9fb205662e3d1 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 14 Apr 2015 22:54:57 +0200 Subject: [PATCH 1/6] Add support for non-square tiles --- changelog/upgrade-notes.md | 4 + examples_src/wms-custom-tilegrid-512x256.html | 13 +++ examples_src/wms-custom-tilegrid-512x256.js | 44 ++++++++++ externs/olx.js | 30 +++---- .../canvas/canvastilelayerrenderer.js | 54 +++++++----- src/ol/renderer/dom/domtilelayerrenderer.js | 26 ++++-- src/ol/renderer/webgl/webglmaprenderer.js | 26 ++++-- .../renderer/webgl/webgltilelayerrenderer.js | 19 ++++- src/ol/size.js | 57 +++++++++++++ src/ol/source/bingmapssource.js | 2 +- src/ol/source/tilearcgisrestsource.js | 14 ++-- src/ol/source/tiledebugsource.js | 12 +-- src/ol/source/tilesource.js | 12 ++- src/ol/source/tilewmssource.js | 23 ++--- src/ol/tilegrid/tilegrid.js | 83 +++++++++++-------- src/ol/tilegrid/wmtstilegrid.js | 8 +- src/ol/tilegrid/xyztilegrid.js | 7 +- test/spec/ol/size.test.js | 65 +++++++++++++++ test/spec/ol/source/tilewmssource.test.js | 16 ++++ 19 files changed, 396 insertions(+), 119 deletions(-) create mode 100644 examples_src/wms-custom-tilegrid-512x256.html create mode 100644 examples_src/wms-custom-tilegrid-512x256.js create mode 100644 test/spec/ol/size.test.js diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 9f7b60fd5a..d473ef0b5b 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -116,6 +116,10 @@ with the `imgSize` option and not with `size`. `size` is supposed to be used for the size of a sub-rectangle in an image sprite. +### Support for non-square tiles + +When using ´ol.tilegrid.TileGrid#getTileSize` on the tile grid of an `ol.source.BingMaps`, the return value will now be an `ol.Size` array instead of a number. + ### v3.4.0 There should be nothing special required when upgrading from v3.3.0 to v3.4.0. diff --git a/examples_src/wms-custom-tilegrid-512x256.html b/examples_src/wms-custom-tilegrid-512x256.html new file mode 100644 index 0000000000..bb3ce43d3b --- /dev/null +++ b/examples_src/wms-custom-tilegrid-512x256.html @@ -0,0 +1,13 @@ +--- +template: example.html +title: WMS custom tile grid 512x256 example +shortdesc: Example of a WMS layer with 512x256px tiles. +docs: > + WMS can serve arbitrary tile sizes. This example uses a custom tile grid with non-square tiles. +tags: "wms, tile, non-square" +--- +
+
+
+
+
diff --git a/examples_src/wms-custom-tilegrid-512x256.js b/examples_src/wms-custom-tilegrid-512x256.js new file mode 100644 index 0000000000..8a6226d030 --- /dev/null +++ b/examples_src/wms-custom-tilegrid-512x256.js @@ -0,0 +1,44 @@ +goog.require('ol.Map'); +goog.require('ol.View'); +goog.require('ol.extent'); +goog.require('ol.layer.Tile'); +goog.require('ol.proj'); +goog.require('ol.source.MapQuest'); +goog.require('ol.source.TileWMS'); +goog.require('ol.tilegrid.TileGrid'); + + +var projExtent = ol.proj.get('EPSG:3857').getExtent(); +var startResolution = ol.extent.getWidth(projExtent) / 256; +var resolutions = new Array(22); +for (var i = 0, ii = resolutions.length; i < ii; ++i) { + resolutions[i] = startResolution / Math.pow(2, i); +} +var tileGrid = new ol.tilegrid.TileGrid({ + origin: ol.extent.getBottomLeft(projExtent), + resolutions: resolutions, + tileSize: [512, 256] +}); + +var layers = [ + new ol.layer.Tile({ + source: new ol.source.MapQuest({layer: 'sat'}) + }), + new ol.layer.Tile({ + extent: [-13884991, 2870341, -7455066, 6338219], + source: new ol.source.TileWMS({ + url: 'http://demo.boundlessgeo.com/geoserver/wms', + params: {'LAYERS': 'topp:states', 'TILED': true}, + serverType: 'geoserver', + tileGrid: tileGrid + }) + }) +]; +var map = new ol.Map({ + layers: layers, + target: 'map', + view: new ol.View({ + center: [-10997148, 4569099], + zoom: 4 + }) +}); diff --git a/externs/olx.js b/externs/olx.js index ebf684f3b2..674b03b8ee 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -5145,7 +5145,7 @@ olx.source.WMTSOptions.prototype.wrapX; * minZoom: (number|undefined), * tileLoadFunction: (ol.TileLoadFunctionType|undefined), * tilePixelRatio: (number|undefined), - * tileSize: (number|undefined), + * tileSize: (number|ol.Size|undefined), * tileUrlFunction: (ol.TileUrlFunctionType|undefined), * url: (string|undefined), * urls: (Array.|undefined), @@ -5227,8 +5227,8 @@ olx.source.XYZOptions.prototype.tilePixelRatio; /** - * The tile size used by the tile service. Default is `256` pixels. - * @type {number|undefined} + * The tile size used by the tile service. Default is `[256, 256]` pixels. + * @type {number|ol.Size|undefined} * @api */ olx.source.XYZOptions.prototype.tileSize; @@ -5924,8 +5924,8 @@ olx.tilegrid; * origin: (ol.Coordinate|undefined), * origins: (Array.|undefined), * resolutions: !Array., - * tileSize: (number|undefined), - * tileSizes: (Array.|undefined), + * tileSize: (number|ol.Size|undefined), + * tileSizes: (Array.|undefined), * widths: (Array.|undefined)}} * @api */ @@ -5968,8 +5968,8 @@ olx.tilegrid.TileGridOptions.prototype.resolutions; /** - * Tile size. Default is 256. (Only square tiles are supported.) - * @type {number|undefined} + * Tile size. Default is `[256, 256]`. + * @type {number|ol.Size|undefined} * @api stable */ olx.tilegrid.TileGridOptions.prototype.tileSize; @@ -5978,7 +5978,7 @@ olx.tilegrid.TileGridOptions.prototype.tileSize; /** * Tile sizes. If given, the array length should match the length of the * `resolutions` array, i.e. each resolution can have a different tile size. - * @type {Array.|undefined} + * @type {Array.|undefined} * @api stable */ olx.tilegrid.TileGridOptions.prototype.tileSizes; @@ -6001,8 +6001,8 @@ olx.tilegrid.TileGridOptions.prototype.widths; * origins: (Array.|undefined), * resolutions: !Array., * matrixIds: !Array., - * tileSize: (number|undefined), - * tileSizes: (Array.|undefined), + * tileSize: (number|ol.Size|undefined), + * tileSizes: (Array.|undefined), * widths: (Array.|undefined)}} * @api */ @@ -6047,7 +6047,7 @@ olx.tilegrid.WMTSOptions.prototype.matrixIds; /** * Tile size. - * @type {number|undefined} + * @type {number|ol.Size|undefined} * @api */ olx.tilegrid.WMTSOptions.prototype.tileSize; @@ -6056,7 +6056,7 @@ olx.tilegrid.WMTSOptions.prototype.tileSize; /** * Tile sizes. The length of this array needs to match the length of the * `resolutions` array. - * @type {Array.|undefined} + * @type {Array.|undefined} * @api */ olx.tilegrid.WMTSOptions.prototype.tileSizes; @@ -6078,7 +6078,7 @@ olx.tilegrid.WMTSOptions.prototype.widths; * @typedef {{extent: (ol.Extent|undefined), * maxZoom: (number|undefined), * minZoom: (number|undefined), - * tileSize: (number|undefined)}} + * tileSize: (number|ol.Size|undefined)}} * @api */ olx.tilegrid.XYZOptions; @@ -6114,8 +6114,8 @@ olx.tilegrid.XYZOptions.prototype.minZoom; /** - * Tile size in pixels. Default is 256. (Only square tiles are supported.) - * @type {number|undefined} + * Tile size in pixels. Default is `[256, 256]`. + * @type {number|ol.Size|undefined} * @api */ olx.tilegrid.XYZOptions.prototype.tileSize; diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js index 7c6bc6d77b..129fe6cdda 100644 --- a/src/ol/renderer/canvas/canvastilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js @@ -14,6 +14,7 @@ goog.require('ol.dom'); goog.require('ol.extent'); goog.require('ol.layer.Tile'); goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.size'); goog.require('ol.tilecoord'); goog.require('ol.vec.Mat4'); @@ -74,7 +75,13 @@ ol.renderer.canvas.TileLayer = function(tileLayer) { * @private * @type {number} */ - this.renderedTileSize_ = NaN; + this.renderedTileWidth_ = NaN; + + /** + * @private + * @type {number} + */ + this.renderedTileHeight_ = NaN; /** * @private @@ -88,6 +95,12 @@ ol.renderer.canvas.TileLayer = function(tileLayer) { */ this.renderedTiles_ = null; + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + }; goog.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer); @@ -190,7 +203,8 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = var z = tileGrid.getZForResolution(viewState.resolution); var tilePixelSize = tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); - var tilePixelRatio = tilePixelSize / tileGrid.getTileSize(z); + var tilePixelRatio = tilePixelSize[0] / + ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0]; var tileResolution = tileGrid.getResolution(z); var tilePixelResolution = tileResolution / tilePixelRatio; var center = viewState.center; @@ -214,8 +228,8 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = var tileRange = tileGrid.getTileRangeForExtentAndResolution( extent, tileResolution); - var canvasWidth = tilePixelSize * tileRange.getWidth(); - var canvasHeight = tilePixelSize * tileRange.getHeight(); + var canvasWidth = tilePixelSize[0] * tileRange.getWidth(); + var canvasHeight = tilePixelSize[1] * tileRange.getHeight(); var canvas, context; if (goog.isNull(this.canvas_)) { @@ -240,7 +254,8 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = context = this.context_; if (this.canvasSize_[0] < canvasWidth || this.canvasSize_[1] < canvasHeight || - this.renderedTileSize_ !== tilePixelSize || + this.renderedTileWidth_ !== tilePixelSize[0] || + this.renderedTileHeight_ !== tilePixelSize[1] || (this.canvasTooBig_ && (this.canvasSize_[0] > canvasWidth || this.canvasSize_[1] > canvasHeight))) { // Canvas is too small or tileSize has changed, resize it. @@ -264,14 +279,15 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = var canvasTileRange, canvasTileRangeWidth, minX, minY; if (goog.isNull(this.renderedCanvasTileRange_)) { - canvasTileRangeWidth = canvasWidth / tilePixelSize; - var canvasTileRangeHeight = canvasHeight / tilePixelSize; + canvasTileRangeWidth = canvasWidth / tilePixelSize[0]; + var canvasTileRangeHeight = canvasHeight / tilePixelSize[1]; minX = tileRange.minX - Math.floor((canvasTileRangeWidth - tileRange.getWidth()) / 2); minY = tileRange.minY - Math.floor((canvasTileRangeHeight - tileRange.getHeight()) / 2); this.renderedCanvasZ_ = z; - this.renderedTileSize_ = tilePixelSize; + this.renderedTileWidth_ = tilePixelSize[0]; + this.renderedTileHeight_ = tilePixelSize[1]; this.renderedCanvasTileRange_ = new ol.TileRange( minX, minX + canvasTileRangeWidth - 1, minY, minY + canvasTileRangeHeight - 1); @@ -332,9 +348,9 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = var i, ii; for (i = 0, ii = tilesToClear.length; i < ii; ++i) { tile = tilesToClear[i]; - x = tilePixelSize * (tile.tileCoord[1] - canvasTileRange.minX); - y = tilePixelSize * (canvasTileRange.maxY - tile.tileCoord[2]); - context.clearRect(x, y, tilePixelSize, tilePixelSize); + x = tilePixelSize[0] * (tile.tileCoord[1] - canvasTileRange.minX); + y = tilePixelSize[1] * (canvasTileRange.maxY - tile.tileCoord[2]); + context.clearRect(x, y, tilePixelSize[0], tilePixelSize[1]); } /** @type {Array.} */ @@ -359,18 +375,18 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = (tile.tileCoord[2] - canvasTileRange.minY) * canvasTileRangeWidth + (tile.tileCoord[1] - canvasTileRange.minX); if (this.renderedTiles_[index] != tile) { - x = tilePixelSize * (tile.tileCoord[1] - canvasTileRange.minX); - y = tilePixelSize * (canvasTileRange.maxY - tile.tileCoord[2]); + x = tilePixelSize[0] * (tile.tileCoord[1] - canvasTileRange.minX); + y = tilePixelSize[1] * (canvasTileRange.maxY - tile.tileCoord[2]); tileState = tile.getState(); if (tileState == ol.TileState.EMPTY || (tileState == ol.TileState.ERROR && !useInterimTilesOnError) || !opaque) { - context.clearRect(x, y, tilePixelSize, tilePixelSize); + context.clearRect(x, y, tilePixelSize[0], tilePixelSize[1]); } if (tileState == ol.TileState.LOADED) { context.drawImage(tile.getImage(), - tileGutter, tileGutter, tilePixelSize, tilePixelSize, - x, y, tilePixelSize, tilePixelSize); + tileGutter, tileGutter, tilePixelSize[0], tilePixelSize[1], + x, y, tilePixelSize[0], tilePixelSize[1]); } this.renderedTiles_[index] = tile; } @@ -382,15 +398,15 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); x = (tileExtent[0] - origin[0]) / tilePixelResolution; y = (origin[1] - tileExtent[3]) / tilePixelResolution; - width = scale * tilePixelSize; - height = scale * tilePixelSize; + width = scale * tilePixelSize[0]; + height = scale * tilePixelSize[1]; tileState = tile.getState(); if (tileState == ol.TileState.EMPTY || !opaque) { context.clearRect(x, y, width, height); } if (tileState == ol.TileState.LOADED) { context.drawImage(tile.getImage(), - tileGutter, tileGutter, tilePixelSize, tilePixelSize, + tileGutter, tileGutter, tilePixelSize[0], tilePixelSize[1], x, y, width, height); } interimTileRange = diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js index 1a58594f7e..89b7861f70 100644 --- a/src/ol/renderer/dom/domtilelayerrenderer.js +++ b/src/ol/renderer/dom/domtilelayerrenderer.js @@ -21,6 +21,7 @@ goog.require('ol.dom.BrowserFeature'); goog.require('ol.extent'); goog.require('ol.layer.Tile'); goog.require('ol.renderer.dom.Layer'); +goog.require('ol.size'); goog.require('ol.tilecoord'); goog.require('ol.tilegrid.TileGrid'); goog.require('ol.vec.Mat4'); @@ -342,6 +343,12 @@ ol.renderer.dom.TileLayerZ_ = function(tileGrid, tileCoordOrigin) { */ this.transform_ = goog.vec.Mat4.createNumberIdentity(); + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + }; @@ -360,7 +367,8 @@ ol.renderer.dom.TileLayerZ_.prototype.addTile = function(tile, tileGutter) { if (tileCoordKey in this.tiles_) { return; } - var tileSize = this.tileGrid_.getTileSize(tileCoordZ); + var tileSize = ol.size.toSize( + this.tileGrid_.getTileSize(tileCoordZ), this.tmpSize_); var image = tile.getImage(this); var imageStyle = image.style; // Bootstrap sets the style max-width: 100% for all images, which @@ -373,25 +381,25 @@ ol.renderer.dom.TileLayerZ_.prototype.addTile = function(tile, tileGutter) { tileElement = goog.dom.createElement(goog.dom.TagName.DIV); tileElementStyle = tileElement.style; tileElementStyle.overflow = 'hidden'; - tileElementStyle.width = tileSize + 'px'; - tileElementStyle.height = tileSize + 'px'; + tileElementStyle.width = tileSize[0] + 'px'; + tileElementStyle.height = tileSize[1] + 'px'; imageStyle.position = 'absolute'; imageStyle.left = -tileGutter + 'px'; imageStyle.top = -tileGutter + 'px'; - imageStyle.width = (tileSize + 2 * tileGutter) + 'px'; - imageStyle.height = (tileSize + 2 * tileGutter) + 'px'; + imageStyle.width = (tileSize[0] + 2 * tileGutter) + 'px'; + imageStyle.height = (tileSize[1] + 2 * tileGutter) + 'px'; goog.dom.appendChild(tileElement, image); } else { - imageStyle.width = tileSize + 'px'; - imageStyle.height = tileSize + 'px'; + imageStyle.width = tileSize[0] + 'px'; + imageStyle.height = tileSize[1] + 'px'; tileElement = image; tileElementStyle = imageStyle; } tileElementStyle.position = 'absolute'; tileElementStyle.left = - ((tileCoordX - this.tileCoordOrigin_[1]) * tileSize) + 'px'; + ((tileCoordX - this.tileCoordOrigin_[1]) * tileSize[0]) + 'px'; tileElementStyle.top = - ((this.tileCoordOrigin_[2] - tileCoordY) * tileSize) + 'px'; + ((this.tileCoordOrigin_[2] - tileCoordY) * tileSize[1]) + 'px'; if (goog.isNull(this.documentFragment_)) { this.documentFragment_ = document.createDocumentFragment(); } diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index 730c1863e9..c90f09536a 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -71,7 +71,13 @@ ol.renderer.webgl.Map = function(container, map) { * @private * @type {number} */ - this.clipTileCanvasSize_ = 0; + this.clipTileCanvasWidth_ = 0; + + /** + * @private + * @type {number} + */ + this.clipTileCanvasHeight_ = 0; /** * @private @@ -158,7 +164,7 @@ ol.renderer.webgl.Map = function(container, map) { this.tileTextureQueue_.reprioritize(); var element = this.tileTextureQueue_.dequeue(); var tile = /** @type {ol.Tile} */ (element[0]); - var tileSize = /** @type {number} */ (element[3]); + var tileSize = /** @type {ol.Size} */ (element[3]); var tileGutter = /** @type {number} */ (element[4]); this.bindTileTexture( tile, tileSize, tileGutter, goog.webgl.LINEAR, goog.webgl.LINEAR); @@ -179,7 +185,7 @@ goog.inherits(ol.renderer.webgl.Map, ol.renderer.Map); /** * @param {ol.Tile} tile Tile. - * @param {number} tileSize Tile size. + * @param {ol.Size} tileSize Tile size. * @param {number} tileGutter Tile gutter. * @param {number} magFilter Mag filter. * @param {number} minFilter Min filter. @@ -209,15 +215,17 @@ ol.renderer.webgl.Map.prototype.bindTileTexture = if (tileGutter > 0) { var clipTileCanvas = this.clipTileContext_.canvas; var clipTileContext = this.clipTileContext_; - if (this.clipTileCanvasSize_ != tileSize) { - clipTileCanvas.width = tileSize; - clipTileCanvas.height = tileSize; - this.clipTileCanvasSize_ = tileSize; + if (this.clipTileCanvasWidth_ !== tileSize[0] || + this.clipTileCanvasHeight_ !== tileSize[1]) { + clipTileCanvas.width = tileSize[0]; + clipTileCanvas.height = tileSize[1]; + this.clipTileCanvasWidth_ = tileSize[0]; + this.clipTileCanvasHeight_ = tileSize[1]; } else { - clipTileContext.clearRect(0, 0, tileSize, tileSize); + clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]); } clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter, - tileSize, tileSize, 0, 0, tileSize, tileSize); + tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]); gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, goog.webgl.RGBA, goog.webgl.UNSIGNED_BYTE, clipTileCanvas); diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index 43f623d286..6627a31584 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -16,6 +16,7 @@ goog.require('ol.layer.Tile'); goog.require('ol.math'); goog.require('ol.renderer.webgl.Layer'); goog.require('ol.renderer.webgl.tilelayer.shader'); +goog.require('ol.size'); goog.require('ol.tilecoord'); goog.require('ol.vec.Mat4'); goog.require('ol.webgl.Buffer'); @@ -80,6 +81,12 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { */ this.renderedRevision_ = -1; + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + }; goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); @@ -160,7 +167,8 @@ ol.renderer.webgl.TileLayer.prototype.prepareFrame = var tilePixelSize = tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); - var pixelRatio = tilePixelSize / tileGrid.getTileSize(z); + var pixelRatio = tilePixelSize[0] / + ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0]; var tilePixelResolution = tileResolution / pixelRatio; var tileGutter = tileSource.getGutter(); @@ -186,12 +194,15 @@ ol.renderer.webgl.TileLayer.prototype.prepareFrame = var tileRangeSize = tileRange.getSize(); var maxDimension = Math.max( - tileRangeSize[0] * tilePixelSize, tileRangeSize[1] * tilePixelSize); + tileRangeSize[0] * tilePixelSize[0], + tileRangeSize[1] * tilePixelSize[1]); var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension); var framebufferExtentDimension = tilePixelResolution * framebufferDimension; var origin = tileGrid.getOrigin(z); - var minX = origin[0] + tileRange.minX * tilePixelSize * tilePixelResolution; - var minY = origin[1] + tileRange.minY * tilePixelSize * tilePixelResolution; + var minX = origin[0] + + tileRange.minX * tilePixelSize[0] * tilePixelResolution; + var minY = origin[1] + + tileRange.minY * tilePixelSize[1] * tilePixelResolution; framebufferExtent = [ minX, minY, minX + framebufferExtentDimension, minY + framebufferExtentDimension diff --git a/src/ol/size.js b/src/ol/size.js index 2fb84be2a7..12d367d9f8 100644 --- a/src/ol/size.js +++ b/src/ol/size.js @@ -2,6 +2,9 @@ goog.provide('ol.Size'); goog.provide('ol.size'); +goog.require('goog.asserts'); + + /** * An array of numbers representing a size: `[width, height]`. * @typedef {Array.} @@ -10,6 +13,23 @@ goog.provide('ol.size'); ol.Size; +/** + * Returns a buffered size. + * @param {ol.Size} size Size. + * @param {number} buffer Buffer. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} + */ +ol.size.buffer = function(size, buffer, opt_size) { + if (!goog.isDef(opt_size)) { + opt_size = [0, 0]; + } + opt_size[0] = size[0] + 2 * buffer; + opt_size[1] = size[1] + 2 * buffer; + return opt_size; +}; + + /** * Compares sizes for equality. * @param {ol.Size} a Size. @@ -19,3 +39,40 @@ ol.Size; ol.size.equals = function(a, b) { return a[0] == b[0] && a[1] == b[1]; }; + + +/** + * Returns a size scaled by a ratio. The result will be an array of integers. + * @param {ol.Size} size Size. + * @param {number} ratio Ratio. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} + */ +ol.size.scale = function(size, ratio, opt_size) { + if (!goog.isDef(opt_size)) { + opt_size = [0, 0]; + } + opt_size[0] = (size[0] * ratio + 0.5) | 0; + opt_size[1] = (size[1] * ratio + 0.5) | 0; + return opt_size; +}; + + +/** + * @param {number|ol.Size} size Width and height. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} Size. + */ +ol.size.toSize = function(size, opt_size) { + if (goog.isArray(size)) { + return size; + } else { + if (!goog.isDef(opt_size)) { + opt_size = [0, 0]; + } + goog.asserts.assert(goog.isNumber(size)); + opt_size[0] = size; + opt_size[1] = size; + return opt_size; + } +}; diff --git a/src/ol/source/bingmapssource.js b/src/ol/source/bingmapssource.js index cf72fce7dd..406102c437 100644 --- a/src/ol/source/bingmapssource.js +++ b/src/ol/source/bingmapssource.js @@ -107,7 +107,7 @@ ol.source.BingMaps.prototype.handleImageryMetadataResponse = extent: ol.tilegrid.extentFromProjection(sourceProjection), minZoom: resource.zoomMin, maxZoom: maxZoom, - tileSize: resource.imageWidth + tileSize: [resource.imageWidth, resource.imageHeight] }); this.tileGrid = tileGrid; diff --git a/src/ol/source/tilearcgisrestsource.js b/src/ol/source/tilearcgisrestsource.js index b07281c88e..4f1a108ab0 100644 --- a/src/ol/source/tilearcgisrestsource.js +++ b/src/ol/source/tilearcgisrestsource.js @@ -11,6 +11,7 @@ goog.require('ol.TileCoord'); goog.require('ol.TileUrlFunction'); goog.require('ol.extent'); goog.require('ol.proj'); +goog.require('ol.size'); goog.require('ol.source.TileImage'); goog.require('ol.tilecoord'); @@ -86,7 +87,7 @@ ol.source.TileArcGISRest.prototype.getParams = function() { /** * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {number} tileSize Tile size. + * @param {ol.Size} tileSize Tile size. * @param {ol.Extent} tileExtent Tile extent. * @param {number} pixelRatio Pixel ratio. * @param {ol.proj.Projection} projection Projection. @@ -106,7 +107,7 @@ ol.source.TileArcGISRest.prototype.getRequestUrl_ = // ArcGIS Server only wants the numeric portion of the projection ID. var srid = projection.getCode().split(':').pop(); - params['SIZE'] = tileSize + ',' + tileSize; + params['SIZE'] = tileSize[0] + ',' + tileSize[1]; params['BBOX'] = tileExtent.join(','); params['BBOXSR'] = srid; params['IMAGESR'] = srid; @@ -143,7 +144,7 @@ ol.source.TileArcGISRest.prototype.getRequestUrl_ = * @param {number} z Z. * @param {number} pixelRatio Pixel ratio. * @param {ol.proj.Projection} projection Projection. - * @return {number} Size. + * @return {ol.Size} Size. */ ol.source.TileArcGISRest.prototype.getTilePixelSize = function(z, pixelRatio, projection) { @@ -151,7 +152,7 @@ ol.source.TileArcGISRest.prototype.getTilePixelSize = if (pixelRatio == 1) { return tileSize; } else { - return (tileSize * pixelRatio + 0.5) | 0; + return ol.size.scale(tileSize, pixelRatio, this.tmpSize); } }; @@ -207,10 +208,11 @@ ol.source.TileArcGISRest.prototype.tileUrlFunction_ = var tileExtent = tileGrid.getTileCoordExtent( tileCoord, this.tmpExtent_); - var tileSize = tileGrid.getTileSize(tileCoord[0]); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); if (pixelRatio != 1) { - tileSize = (tileSize * pixelRatio + 0.5) | 0; + tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize); } // Apply default params and override with user specified values. diff --git a/src/ol/source/tiledebugsource.js b/src/ol/source/tiledebugsource.js index ae49401d95..b035f37efd 100644 --- a/src/ol/source/tiledebugsource.js +++ b/src/ol/source/tiledebugsource.js @@ -4,6 +4,7 @@ goog.require('ol.Tile'); goog.require('ol.TileCoord'); goog.require('ol.TileState'); goog.require('ol.dom'); +goog.require('ol.size'); goog.require('ol.source.Tile'); goog.require('ol.tilecoord'); goog.require('ol.tilegrid.TileGrid'); @@ -23,9 +24,10 @@ ol.DebugTile_ = function(tileCoord, tileGrid) { /** * @private - * @type {number} + * @type {ol.Size} */ - this.tileSize_ = tileGrid.getTileSize(tileCoord[0]); + this.tileSize_ = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0])); /** * @private @@ -47,17 +49,17 @@ ol.DebugTile_.prototype.getImage = function(opt_context) { } else { var tileSize = this.tileSize_; - var context = ol.dom.createCanvasContext2D(tileSize, tileSize); + var context = ol.dom.createCanvasContext2D(tileSize[0], tileSize[1]); context.strokeStyle = 'black'; - context.strokeRect(0.5, 0.5, tileSize + 0.5, tileSize + 0.5); + context.strokeRect(0.5, 0.5, tileSize[0] + 0.5, tileSize[1] + 0.5); context.fillStyle = 'black'; context.textAlign = 'center'; context.textBaseline = 'middle'; context.font = '24px sans-serif'; context.fillText(ol.tilecoord.toString(this.tileCoord), - tileSize / 2, tileSize / 2); + tileSize[0] / 2, tileSize[1] / 2); this.canvasByContext_[key] = context.canvas; return context.canvas; diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js index 4e8d0988c8..0a9a546b11 100644 --- a/src/ol/source/tilesource.js +++ b/src/ol/source/tilesource.js @@ -8,6 +8,7 @@ goog.require('ol.Extent'); goog.require('ol.TileCache'); goog.require('ol.TileRange'); goog.require('ol.TileState'); +goog.require('ol.size'); goog.require('ol.source.Source'); goog.require('ol.tilecoord'); goog.require('ol.tilegrid.TileGrid'); @@ -74,6 +75,12 @@ ol.source.Tile = function(options) { */ this.tileCache = new ol.TileCache(); + /** + * @protected + * @type {ol.Size} + */ + this.tmpSize = [0, 0]; + /** * @private * @type {boolean|undefined} @@ -202,12 +209,13 @@ ol.source.Tile.prototype.getTileGridForProjection = function(projection) { * @param {number} z Z. * @param {number} pixelRatio Pixel ratio. * @param {ol.proj.Projection} projection Projection. - * @return {number} Tile size. + * @return {ol.Size} Tile size. */ ol.source.Tile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { var tileGrid = this.getTileGridForProjection(projection); - return tileGrid.getTileSize(z) * this.tilePixelRatio_; + return ol.size.scale(ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize), + this.tilePixelRatio_, this.tmpSize); }; diff --git a/src/ol/source/tilewmssource.js b/src/ol/source/tilewmssource.js index aa41a1b5ff..8cdcd9ca58 100644 --- a/src/ol/source/tilewmssource.js +++ b/src/ol/source/tilewmssource.js @@ -15,6 +15,7 @@ goog.require('ol.TileCoord'); goog.require('ol.TileUrlFunction'); goog.require('ol.extent'); goog.require('ol.proj'); +goog.require('ol.size'); goog.require('ol.source.TileImage'); goog.require('ol.source.wms'); goog.require('ol.source.wms.ServerType'); @@ -148,11 +149,12 @@ ol.source.TileWMS.prototype.getGetFeatureInfoUrl = var tileResolution = tileGrid.getResolution(tileCoord[0]); var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_); - var tileSize = tileGrid.getTileSize(tileCoord[0]); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); var gutter = this.gutter_; if (gutter !== 0) { - tileSize += 2 * gutter; + tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize); tileExtent = ol.extent.buffer(tileExtent, tileResolution * gutter, tileExtent); } @@ -207,7 +209,7 @@ ol.source.TileWMS.prototype.getParams = function() { /** * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {number} tileSize Tile size. + * @param {ol.Size} tileSize Tile size. * @param {ol.Extent} tileExtent Tile extent. * @param {number} pixelRatio Pixel ratio. * @param {ol.proj.Projection} projection Projection. @@ -224,8 +226,8 @@ ol.source.TileWMS.prototype.getRequestUrl_ = return undefined; } - params['WIDTH'] = tileSize; - params['HEIGHT'] = tileSize; + params['WIDTH'] = tileSize[0]; + params['HEIGHT'] = tileSize[1]; params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode(); @@ -282,7 +284,7 @@ ol.source.TileWMS.prototype.getRequestUrl_ = * @param {number} z Z. * @param {number} pixelRatio Pixel ratio. * @param {ol.proj.Projection} projection Projection. - * @return {number} Size. + * @return {ol.Size} Size. */ ol.source.TileWMS.prototype.getTilePixelSize = function(z, pixelRatio, projection) { @@ -290,7 +292,7 @@ ol.source.TileWMS.prototype.getTilePixelSize = if (pixelRatio == 1 || !this.hidpi_ || !goog.isDef(this.serverType_)) { return tileSize; } else { - return (tileSize * pixelRatio + 0.5) | 0; + return ol.size.scale(tileSize, pixelRatio, this.tmpSize); } }; @@ -372,17 +374,18 @@ ol.source.TileWMS.prototype.tileUrlFunction_ = var tileResolution = tileGrid.getResolution(tileCoord[0]); var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_); - var tileSize = tileGrid.getTileSize(tileCoord[0]); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); var gutter = this.gutter_; if (gutter !== 0) { - tileSize += 2 * gutter; + tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize); tileExtent = ol.extent.buffer(tileExtent, tileResolution * gutter, tileExtent); } if (pixelRatio != 1) { - tileSize = (tileSize * pixelRatio + 0.5) | 0; + tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize); } var baseParams = { diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index ff0f894cff..b8a112e5b4 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -15,6 +15,7 @@ goog.require('ol.proj'); goog.require('ol.proj.METERS_PER_UNIT'); goog.require('ol.proj.Projection'); goog.require('ol.proj.Units'); +goog.require('ol.size'); goog.require('ol.tilecoord'); @@ -75,7 +76,7 @@ ol.tilegrid.TileGrid = function(options) { /** * @private - * @type {Array.} + * @type {Array.} */ this.tileSizes_ = null; if (goog.isDef(options.tileSizes)) { @@ -86,16 +87,23 @@ ol.tilegrid.TileGrid = function(options) { /** * @private - * @type {number|undefined} + * @type {number|ol.Size} */ this.tileSize_ = goog.isDef(options.tileSize) ? options.tileSize : - goog.isNull(this.tileSizes_) ? ol.DEFAULT_TILE_SIZE : undefined; + goog.isNull(this.tileSizes_) ? ol.DEFAULT_TILE_SIZE : null; goog.asserts.assert( - (!goog.isDef(this.tileSize_) && !goog.isNull(this.tileSizes_)) || - (goog.isDef(this.tileSize_) && goog.isNull(this.tileSizes_)), + (goog.isNull(this.tileSize_) && !goog.isNull(this.tileSizes_)) || + (!goog.isNull(this.tileSize_) && goog.isNull(this.tileSizes_)), 'either tileSize or tileSizes must be configured, never both'); + + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + /** * @private * @type {Array.} @@ -245,11 +253,11 @@ ol.tilegrid.TileGrid.prototype.getTileRangeExtent = function(z, tileRange, opt_extent) { var origin = this.getOrigin(z); var resolution = this.getResolution(z); - var tileSize = this.getTileSize(z); - var minX = origin[0] + tileRange.minX * tileSize * resolution; - var maxX = origin[0] + (tileRange.maxX + 1) * tileSize * resolution; - var minY = origin[1] + tileRange.minY * tileSize * resolution; - var maxY = origin[1] + (tileRange.maxY + 1) * tileSize * resolution; + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + var minX = origin[0] + tileRange.minX * tileSize[0] * resolution; + var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution; + var minY = origin[1] + tileRange.minY * tileSize[1] * resolution; + var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution; return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; @@ -295,10 +303,10 @@ ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) { var origin = this.getOrigin(tileCoord[0]); var resolution = this.getResolution(tileCoord[0]); - var tileSize = this.getTileSize(tileCoord[0]); + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); return [ - origin[0] + (tileCoord[1] + 0.5) * tileSize * resolution, - origin[1] + (tileCoord[2] + 0.5) * tileSize * resolution + origin[0] + (tileCoord[1] + 0.5) * tileSize[0] * resolution, + origin[1] + (tileCoord[2] + 0.5) * tileSize[1] * resolution ]; }; @@ -312,11 +320,11 @@ ol.tilegrid.TileGrid.prototype.getTileCoordExtent = function(tileCoord, opt_extent) { var origin = this.getOrigin(tileCoord[0]); var resolution = this.getResolution(tileCoord[0]); - var tileSize = this.getTileSize(tileCoord[0]); - var minX = origin[0] + tileCoord[1] * tileSize * resolution; - var minY = origin[1] + tileCoord[2] * tileSize * resolution; - var maxX = minX + tileSize * resolution; - var maxY = minY + tileSize * resolution; + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); + var minX = origin[0] + tileCoord[1] * tileSize[0] * resolution; + var minY = origin[1] + tileCoord[2] * tileSize[1] * resolution; + var maxX = minX + tileSize[0] * resolution; + var maxY = minY + tileSize[1] * resolution; return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; @@ -355,10 +363,10 @@ ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function( var z = this.getZForResolution(resolution); var scale = resolution / this.getResolution(z); var origin = this.getOrigin(z); - var tileSize = this.getTileSize(z); + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); - var tileCoordX = scale * (x - origin[0]) / (resolution * tileSize); - var tileCoordY = scale * (y - origin[1]) / (resolution * tileSize); + var tileCoordX = scale * (x - origin[0]) / (resolution * tileSize[0]); + var tileCoordY = scale * (y - origin[1]) / (resolution * tileSize[1]); if (reverseIntersectionPolicy) { tileCoordX = Math.ceil(tileCoordX) - 1; @@ -421,9 +429,10 @@ ol.tilegrid.TileGrid.prototype.getTileRange = /** - * Get the tile size for a zoom level. + * Get the tile size for a zoom level. The type of the return value matches the + * `tileSize` or `tileSizes` that the tile grid was configured with. * @param {number} z Z. - * @return {number} Tile size. + * @return {number|ol.Size} Tile size. * @api stable */ ol.tilegrid.TileGrid.prototype.getTileSize = function(z) { @@ -476,7 +485,7 @@ ol.tilegrid.TileGrid.prototype.isGlobal = function(z, projection) { if (goog.isDef(width)) { var projTileGrid = ol.tilegrid.getForProjection(projection); var projExtent = projection.getExtent(); - return this.getTileSize(z) * width == + return ol.size.toSize(this.getTileSize(z), this.tmpSize_)[0] * width == projTileGrid.getTileSize(z) * projTileGrid.getTileRangeForExtentAndZ(projExtent, z).getWidth(); } else { @@ -503,7 +512,8 @@ ol.tilegrid.getForProjection = function(projection) { * @param {ol.Extent} extent Extent. * @param {number=} opt_maxZoom Maximum zoom level (default is * ol.DEFAULT_MAX_ZOOM). - * @param {number=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). * @param {ol.extent.Corner=} opt_corner Extent corner (default is * ol.extent.Corner.BOTTOM_LEFT). * @return {ol.tilegrid.TileGrid} TileGrid instance. @@ -511,24 +521,24 @@ ol.tilegrid.getForProjection = function(projection) { ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) { var tileSize = goog.isDef(opt_tileSize) ? - opt_tileSize : ol.DEFAULT_TILE_SIZE; + ol.size.toSize(opt_tileSize) : ol.tilegrid.DEFAULT_TILE_SIZE; var corner = goog.isDef(opt_corner) ? opt_corner : ol.extent.Corner.BOTTOM_LEFT; var resolutions = ol.tilegrid.resolutionsFromExtent( - extent, opt_maxZoom, tileSize); + extent, opt_maxZoom, ol.size.toSize(tileSize)); var widths = new Array(resolutions.length); var extentWidth = ol.extent.getWidth(extent); for (var z = resolutions.length - 1; z >= 0; --z) { - widths[z] = extentWidth / tileSize / resolutions[z]; + widths[z] = extentWidth / tileSize[0] / resolutions[z]; } return new ol.tilegrid.TileGrid({ origin: ol.extent.getCorner(extent, corner), resolutions: resolutions, - tileSize: tileSize, + tileSize: goog.isDef(opt_tileSize) ? opt_tileSize : ol.DEFAULT_TILE_SIZE, widths: widths }); }; @@ -539,7 +549,7 @@ ol.tilegrid.createForExtent = * @param {ol.Extent} extent Extent. * @param {number=} opt_maxZoom Maximum zoom level (default is * ol.DEFAULT_MAX_ZOOM). - * @param {number=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). + * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). * @return {!Array.} Resolutions array. */ ol.tilegrid.resolutionsFromExtent = @@ -551,9 +561,9 @@ ol.tilegrid.resolutionsFromExtent = var width = ol.extent.getWidth(extent); var tileSize = goog.isDef(opt_tileSize) ? - opt_tileSize : ol.DEFAULT_TILE_SIZE; + opt_tileSize : ol.tilegrid.DEFAULT_TILE_SIZE; var maxResolution = Math.max( - width / tileSize, height / tileSize); + width / tileSize[0], height / tileSize[1]); var length = maxZoom + 1; var resolutions = new Array(length); @@ -568,7 +578,7 @@ ol.tilegrid.resolutionsFromExtent = * @param {ol.proj.ProjectionLike} projection Projection. * @param {number=} opt_maxZoom Maximum zoom level (default is * ol.DEFAULT_MAX_ZOOM). - * @param {number=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). + * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). * @param {ol.extent.Corner=} opt_corner Extent corner (default is * ol.extent.Corner.BOTTOM_LEFT). * @return {ol.tilegrid.TileGrid} TileGrid instance. @@ -597,3 +607,10 @@ ol.tilegrid.extentFromProjection = function(projection) { } return extent; }; + + +/** + * @const + * @type {ol.Size} + */ +ol.tilegrid.DEFAULT_TILE_SIZE = ol.size.toSize(ol.DEFAULT_TILE_SIZE); diff --git a/src/ol/tilegrid/wmtstilegrid.js b/src/ol/tilegrid/wmtstilegrid.js index 95e5d999c8..6770358784 100644 --- a/src/ol/tilegrid/wmtstilegrid.js +++ b/src/ol/tilegrid/wmtstilegrid.js @@ -81,7 +81,7 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = var matrixIds = []; /** @type {!Array.} */ var origins = []; - /** @type {!Array.} */ + /** @type {!Array.} */ var tileSizes = []; /** @type {!Array.} */ var widths = []; @@ -118,10 +118,8 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = metersPerUnit); var tileWidth = elt[tileWidthPropName]; var tileHeight = elt[tileHeightPropName]; - goog.asserts.assert(tileWidth == tileHeight, - 'square tiles are assumed, tileWidth (%s) == tileHeight (%s)', - tileWidth, tileHeight); - tileSizes.push(tileWidth); + tileSizes.push(tileWidth == tileHeight ? + tileWidth : [tileWidth, tileHeight]); widths.push(elt['MatrixWidth']); }); diff --git a/src/ol/tilegrid/xyztilegrid.js b/src/ol/tilegrid/xyztilegrid.js index 6fec56bcd6..b10a398c67 100644 --- a/src/ol/tilegrid/xyztilegrid.js +++ b/src/ol/tilegrid/xyztilegrid.js @@ -8,6 +8,7 @@ goog.require('ol.extent'); goog.require('ol.extent.Corner'); goog.require('ol.proj'); goog.require('ol.proj.EPSG3857'); +goog.require('ol.size'); goog.require('ol.tilecoord'); goog.require('ol.tilegrid.TileGrid'); @@ -26,8 +27,12 @@ goog.require('ol.tilegrid.TileGrid'); ol.tilegrid.XYZ = function(options) { var extent = goog.isDef(options.extent) ? options.extent : ol.proj.EPSG3857.EXTENT; + var tileSize; + if (goog.isDef(options.tileSize)) { + tileSize = ol.size.toSize(options.tileSize); + } var resolutions = ol.tilegrid.resolutionsFromExtent( - extent, options.maxZoom, options.tileSize); + extent, options.maxZoom, tileSize); goog.base(this, { minZoom: options.minZoom, diff --git a/test/spec/ol/size.test.js b/test/spec/ol/size.test.js new file mode 100644 index 0000000000..cd21e9d6b0 --- /dev/null +++ b/test/spec/ol/size.test.js @@ -0,0 +1,65 @@ +goog.provide('ol.test.size'); + + +describe('ol.size', function() { + + describe('#buffer()', function() { + + it('buffers a size', function() { + var size = [50, 75]; + var bufferedSize = ol.size.buffer(size, 20); + expect(bufferedSize).to.eql([90, 115]); + }); + + it('reuses an existing array', function() { + var reuse = [0, 0]; + var size = [50, 50]; + var bufferedSize = ol.size.buffer(size, 20, reuse); + expect(bufferedSize).to.equal(reuse); + }); + + }); + + describe('#scale()', function() { + + it('scales a size and rounds the result', function() { + var size = [50, 75]; + var scaledSize = ol.size.scale(size, 1.75); + expect(scaledSize).to.eql([88, 131]); + }); + + it('reuses an existing array', function() { + var reuse = [0, 0]; + var size = [50, 50]; + var scaledSize = ol.size.scale(size, 1.75, reuse); + expect(scaledSize).to.equal(reuse); + }); + + }); + + describe('#toSize()', function() { + + it('creates a size array from a number', function() { + var size = ol.size.toSize(512); + expect(size).to.eql([512, 512]); + }); + + it('reuses an existing array', function() { + var sizeArray = [0, 0]; + var size = ol.size.toSize(512, sizeArray); + expect(size).to.equal(sizeArray); + }); + + it('returns a size array unaltered', function() { + var sizeArray = [512, 256]; + var size = ol.size.toSize(sizeArray); + expect(size).to.equal(sizeArray); + size = ol.size.toSize(sizeArray, [0, 0]); + expect(size).to.equal(sizeArray); + }); + + }); + +}); + +goog.require('ol.size'); diff --git a/test/spec/ol/source/tilewmssource.test.js b/test/spec/ol/source/tilewmssource.test.js index 069248be8e..8a5c547218 100644 --- a/test/spec/ol/source/tilewmssource.test.js +++ b/test/spec/ol/source/tilewmssource.test.js @@ -129,6 +129,21 @@ describe('ol.source.TileWMS', function() { expect(queryData.get('BBOX')).to.be('-45,-45,0,0'); }); + it('works with non-square tiles', function() { + options.tileGrid = new ol.tilegrid.TileGrid({ + tileSize: [640, 320], + resolutions: [1.40625, 0.703125, 0.3515625, 0.17578125], + origin: [-180, -90] + }); + var source = new ol.source.TileWMS(options); + var tileCoord = [3, 3, 1]; + var url = source.tileUrlFunction(tileCoord, 1, ol.proj.get('EPSG:4326')); + var uri = new goog.Uri(url); + var queryData = uri.getQueryData(); + expect(queryData.get('WIDTH')).to.be('640'); + expect(queryData.get('HEIGHT')).to.be('320'); + }); + }); describe('#getGetFeatureInfo', function() { @@ -205,3 +220,4 @@ goog.require('goog.Uri'); goog.require('ol.ImageTile'); goog.require('ol.source.TileWMS'); goog.require('ol.proj'); +goog.require('ol.tilegrid.TileGrid'); From 0d53edfa21d535542b00f4d644636404717700bc Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 14 Apr 2015 23:16:29 +0200 Subject: [PATCH 2/6] Get rid of ol.tilegrid.DEFAULT_TILE_SIZE --- src/ol/tilegrid/tilegrid.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index b8a112e5b4..f4e0f9637c 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -521,7 +521,7 @@ ol.tilegrid.getForProjection = function(projection) { ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) { var tileSize = goog.isDef(opt_tileSize) ? - ol.size.toSize(opt_tileSize) : ol.tilegrid.DEFAULT_TILE_SIZE; + ol.size.toSize(opt_tileSize) : ol.size.toSize(ol.DEFAULT_TILE_SIZE); var corner = goog.isDef(opt_corner) ? opt_corner : ol.extent.Corner.BOTTOM_LEFT; @@ -561,7 +561,7 @@ ol.tilegrid.resolutionsFromExtent = var width = ol.extent.getWidth(extent); var tileSize = goog.isDef(opt_tileSize) ? - opt_tileSize : ol.tilegrid.DEFAULT_TILE_SIZE; + opt_tileSize : ol.size.toSize(ol.DEFAULT_TILE_SIZE); var maxResolution = Math.max( width / tileSize[0], height / tileSize[1]); @@ -607,10 +607,3 @@ ol.tilegrid.extentFromProjection = function(projection) { } return extent; }; - - -/** - * @const - * @type {ol.Size} - */ -ol.tilegrid.DEFAULT_TILE_SIZE = ol.size.toSize(ol.DEFAULT_TILE_SIZE); From 9c415ac9da094632ecfd0cf5d691404c70908d55 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 15 Apr 2015 08:12:17 +0200 Subject: [PATCH 3/6] Use simple tile sizes for Bing maps by default --- changelog/upgrade-notes.md | 2 +- src/ol/source/bingmapssource.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index d473ef0b5b..247368e163 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -118,7 +118,7 @@ size of a sub-rectangle in an image sprite. ### Support for non-square tiles -When using ´ol.tilegrid.TileGrid#getTileSize` on the tile grid of an `ol.source.BingMaps`, the return value will now be an `ol.Size` array instead of a number. +The return value of ´ol.tilegrid.TileGrid#getTileSize()` will now be an `ol.Size` array instead of a number if non-square tiles (i.e. an `ol.Size` array instead of a number as `tilsSize`) are used. To always get an `ol.Size`, the new `ol.size.toSize()` was added. ### v3.4.0 diff --git a/src/ol/source/bingmapssource.js b/src/ol/source/bingmapssource.js index 406102c437..f1eb3f415d 100644 --- a/src/ol/source/bingmapssource.js +++ b/src/ol/source/bingmapssource.js @@ -107,7 +107,8 @@ ol.source.BingMaps.prototype.handleImageryMetadataResponse = extent: ol.tilegrid.extentFromProjection(sourceProjection), minZoom: resource.zoomMin, maxZoom: maxZoom, - tileSize: [resource.imageWidth, resource.imageHeight] + tileSize: resource.imageWidth == resource.imageHeight ? + resource.imageWidth : [resource.imageWidth, resource.imageHeight] }); this.tileGrid = tileGrid; From ddf5e48830ddca8b97154365c518b3ae726b83e1 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 15 Apr 2015 08:28:31 +0200 Subject: [PATCH 4/6] Make ol.size.toSize exportable and optimize it --- src/ol/size.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ol/size.js b/src/ol/size.js index 12d367d9f8..d2e008ab11 100644 --- a/src/ol/size.js +++ b/src/ol/size.js @@ -59,20 +59,25 @@ ol.size.scale = function(size, ratio, opt_size) { /** + * Returns an `ol.Size` array for the passed in number (meaning: square) or + * `ol.Size` array. + * (meaning: non-square), * @param {number|ol.Size} size Width and height. * @param {ol.Size=} opt_size Optional reusable size array. * @return {ol.Size} Size. + * @api stable */ ol.size.toSize = function(size, opt_size) { if (goog.isArray(size)) { return size; } else { - if (!goog.isDef(opt_size)) { - opt_size = [0, 0]; - } goog.asserts.assert(goog.isNumber(size)); - opt_size[0] = size; - opt_size[1] = size; + if (!goog.isDef(opt_size)) { + opt_size = [size, size]; + } else { + opt_size[0] = size; + opt_size[1] = size; + } return opt_size; } }; From 509a661fda21477a95d5ca48882103ccb37cfdff Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 15 Apr 2015 08:40:20 +0200 Subject: [PATCH 5/6] Reference ol.size.toSize() --- src/ol/tilegrid/tilegrid.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index f4e0f9637c..3db5bd3d7a 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -430,7 +430,8 @@ ol.tilegrid.TileGrid.prototype.getTileRange = /** * Get the tile size for a zoom level. The type of the return value matches the - * `tileSize` or `tileSizes` that the tile grid was configured with. + * `tileSize` or `tileSizes` that the tile grid was configured with. To always + * get an `ol.Size`, run the result through `ol.size.toSize()`. * @param {number} z Z. * @return {number|ol.Size} Tile size. * @api stable From 70452f4fa7bb09680a497ab3594eb9cfc28c8551 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Thu, 16 Apr 2015 09:15:34 +0200 Subject: [PATCH 6/6] Add rendering tests --- .../spec/ol/data/tiles/512x256/5/3/19.png | Bin 0 -> 19028 bytes .../spec/ol/layer/expected/512x256-canvas.png | Bin 0 -> 806 bytes .../spec/ol/layer/expected/512x256-webgl.png | Bin 0 -> 806 bytes test_rendering/spec/ol/layer/tile.test.js | 50 +++++++++++++++++- 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 test_rendering/spec/ol/data/tiles/512x256/5/3/19.png create mode 100644 test_rendering/spec/ol/layer/expected/512x256-canvas.png create mode 100644 test_rendering/spec/ol/layer/expected/512x256-webgl.png diff --git a/test_rendering/spec/ol/data/tiles/512x256/5/3/19.png b/test_rendering/spec/ol/data/tiles/512x256/5/3/19.png new file mode 100644 index 0000000000000000000000000000000000000000..00f5a3285720396298a142c217ff69752fcd9964 GIT binary patch literal 19028 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 literal 0 HcmV?d00001 diff --git a/test_rendering/spec/ol/layer/expected/512x256-canvas.png b/test_rendering/spec/ol/layer/expected/512x256-canvas.png new file mode 100644 index 0000000000000000000000000000000000000000..e78df46f5178195916ef2552887f8f14fb6ceb80 GIT binary patch literal 806 zcmV+>1KIqEP)T!gSFdWrtkL4*`?SQ+@G83a8WWRqO5EH(FMRySU3LAs+T));IawJP z{xbow1|Q$&R9V@__97ymusN5J9v*>NEU|N^Q?QiOlc~D8w?JY)|NdnRJ9kc3hJ)h= zOzhRKU##kT_d4V$DBNz;)O-L^2NH8UcFfE{Sok$e9lB=*%p>o9{bKPsecCWkLgIO; zvhov{!{$DCAdzzUvdIrGudVFN%>QBHE1y0U0mc+>yO!4dK@%gzN=gs?&zvzXS5dhg zBQ5X+mfk_hO>^HqyZb+W z@LCE9y#&QrgtYW4Sq_e0_&h~-k9_$3o9*}i|IAZ#b)@P~pJOgv=)sG*!<~)5WIsM)}Rb?KY z&sQBBjt&ZsfJ&`CEv?%gr%oAR^9-njdieD#yQ!ey7i0(5U%#%&!fNA|z ziIUQNP)0}gOoO`m6+0oJ_iumyW(_`b#70h=07`Ly%Nm1d|A&=Qu5h= z)+3;FKJC#X8CXavaB+Qu)g2(u)ZDtI`0dXh7LYiwB7C~x#S7tGuU?7V`|*SOiBeV=%a?Bo$5pqI}mU>2GgqER8j@hFzLQbgBQqO3N&@yz%F?%#d$O$!C z>KTm@T81t;W{<`QIiW^NJ)dNjK&Bp kLzf)0M`MJXP@|1KIqEP)T!gSFdWrtkL4*`?SQ+@G83a8WWRqO5EH(FMRySU3LAs+T));IawJP z{xbow1|Q$&R9V@__97ymusN5J9v*>NEU|N^Q?QiOlc~D8w?JY)|NdnRJ9kc3hJ)h= zOzhRKU##kT_d4V$DBNz;)O-L^2NH8UcFfE{Sok$e9lB=*%p>o9{bKPsecCWkLgIO; zvhov{!{$DCAdzzUvdIrGudVFN%>QBHE1y0U0mc+>yO!4dK@%gzN=gs?&zvzXS5dhg zBQ5X+mfk_hO>^HqyZb+W z@LCE9y#&QrgtYW4Sq_e0_&h~-k9_$3o9*}i|IAZ#b)@P~pJOgv=)sG*!<~)5WIsM)}Rb?KY z&sQBBjt&ZsfJ&`CEv?%gr%oAR^9-njdieD#yQ!ey7i0(5U%#%&!fNA|z ziIUQNP)0}gOoO`m6+0oJ_iumyW(_`b#70h=07`Ly%Nm1d|A&=Qu5h= z)+3;FKJC#X8CXavaB+Qu)g2(u)ZDtI`0dXh7LYiwB7C~x#S7tGuU?7V`|*SOiBeV=%a?Bo$5pqI}mU>2GgqER8j@hFzLQbgBQqO3N&@yz%F?%#d$O$!C z>KTm@T81t;W{<`QIiW^NJ)dNjK&Bp kLzf)0M`MJXP@|