diff --git a/src/ol/dom/layerrenderer.js b/src/ol/dom/layerrenderer.js new file mode 100644 index 0000000000..c2a368206a --- /dev/null +++ b/src/ol/dom/layerrenderer.js @@ -0,0 +1,56 @@ +goog.provide('ol.dom.LayerRenderer'); + +goog.require('ol.LayerRenderer'); + + + +/** + * @constructor + * @extends {ol.LayerRenderer} + * @param {ol.dom.Map} map Map. + * @param {ol.Layer} layer Layer. + * @param {!Element} target Target. + */ +ol.dom.LayerRenderer = function(map, layer, target) { + goog.base(this, map, layer); + + /** + * @type {!Element} + * @protected + */ + this.target = target; + + /** + * Location of the top-left corner of the renderer container in map coords. + * + * @type {goog.math.Coordinate} + * @protected + */ + this.containerOrigin = null; + +}; +goog.inherits(ol.dom.LayerRenderer, ol.LayerRenderer); + + +/** + * @override + * @return {ol.dom.Map} Map. + */ +ol.dom.LayerRenderer.prototype.getMap = function() { + return /** @type {ol.dom.Map} */ (goog.base(this, 'getMap')); +}; + + +/** + * Set the location of the top-left corner of the renderer container. + * + * @param {goog.math.Coordinate} origin The container origin. + */ +ol.dom.LayerRenderer.prototype.setContainerOrigin = function(origin) { + this.containerOrigin = origin; +}; + + +/** + */ +ol.dom.LayerRenderer.prototype.redraw = goog.abstractMethod; diff --git a/src/ol/dom/map.js b/src/ol/dom/map.js index 1a8c66f2f0..617ff6b163 100644 --- a/src/ol/dom/map.js +++ b/src/ol/dom/map.js @@ -1,8 +1,12 @@ goog.provide('ol.dom.Map'); -goog.require('ol.Layer'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.math.Coordinate'); +goog.require('goog.style'); goog.require('ol.Map'); -goog.require('ol.TileStore'); +goog.require('ol.TileLayer'); +goog.require('ol.dom.TileLayerRenderer'); @@ -13,26 +17,197 @@ goog.require('ol.TileStore'); * @param {Object.=} opt_values Values. */ ol.dom.Map = function(target, opt_values) { - goog.base(this, target); - // FIXME write initialization code here + /** + * @type {Element} + * @private + */ + this.layersPane_ = goog.dom.createElement(goog.dom.TagName.DIV); + this.layersPane_.style.position = 'absolute'; + this.layersPane_.className = 'ol-renderer-dom'; + target.appendChild(this.layersPane_); + + /** + * The current top left corner location of the layersPane element + * (map coords). + * + * @type {goog.math.Coordinate} + * @private + */ + this.layersPaneOrigin_ = null; + + /** + * The pixel offset of the layersPane element with respect to its + * container. + * + * @type {goog.math.Coordinate} + * @private + */ + this.layersPaneOffset_ = null; + + /** + * @type {goog.math.Coordinate|undefined} + * @private + */ + this.renderedCenter_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.renderedResolution_ = undefined; + + /** + * @type {Object} + * @private + */ + this.layerPanes_ = {}; if (goog.isDef(opt_values)) { this.setValues(opt_values); } + this.handleViewportResize(); + }; goog.inherits(ol.dom.Map, ol.Map); +/** + * Reset the layers pane to its initial position. + * @private + */ +ol.dom.Map.prototype.resetLayersPane_ = function() { + this.layersPaneOrigin_ = this.getOrigin_(); + goog.object.forEach(this.layerRenderers, function(layerRenderer) { + layerRenderer.setContainerOrigin(this.layersPaneOrigin_); + }); + var offset = new goog.math.Coordinate(0, 0); + goog.style.setPosition(this.layersPane_, offset); + this.renderedCenter_ = this.getCenter(); +}; + + +/** + * Get the position of the top-left corner of the layers pane. + * @private + * @return {goog.math.Coordinate} Origin. + */ +ol.dom.Map.prototype.getOrigin_ = function() { + var center = this.getCenter(); + var resolution = this.getResolution(); + var targetSize = this.getSize(); + var targetWidth = targetSize.width; + var targetHeight = targetSize.height; + return new goog.math.Coordinate( + center.x - resolution * targetWidth / 2, + center.y + resolution * targetHeight / 2); +}; + + +/** + * Move the layers pane. + * @private + */ +ol.dom.Map.prototype.shiftLayersPane_ = function() { + var center = this.getCenter(); + var oldCenter = this.renderedCenter_; + var resolution = this.getResolution(); + var dx = Math.round((oldCenter.x - center.x) / resolution); + var dy = Math.round((center.y - oldCenter.y) / resolution); + if (!(dx === 0 && dy === 0)) { + var offset = this.layersPaneOffset_; + offset.x += Math.round((oldCenter.x - center.x) / resolution); + offset.y += Math.round((center.y - oldCenter.y) / resolution); + goog.style.setPosition(this.layersPane_, offset); + } +}; + + /** * @inheritDoc */ ol.dom.Map.prototype.createLayerRenderer = function(layer) { - var store = layer.getStore(); - if (layer instanceof ol.TileStore) { - // FIXME create ol.dom.TileLayerRenderer + + if (layer instanceof ol.TileLayer) { + + var layerPane = goog.dom.createElement(goog.dom.TagName.DIV); + layerPane.className = 'ol-renderer-dom-layer'; + layerPane.style.position = 'absolute'; + layerPane.style.width = '100%'; + layerPane.style.height = '100%'; + layerPane.style.top = 0 + 'px'; + layerPane.style.left = 0 + 'px'; + this.layersPane_.appendChild(layerPane); + + var layerRenderer = new ol.dom.TileLayerRenderer(this, layer, layerPane); + layerRenderer.setContainerOrigin(this.layersPaneOrigin_); + + this.layerPanes_[goog.getUid(layerRenderer)] = layerPane; + + return layerRenderer; + + } else { + goog.asserts.assert(false); + return null; } - return null; +}; + + +/** + * @inheritDoc + */ +ol.dom.Map.prototype.handleCenterChanged = function() { + goog.base(this, 'handleCenterChanged'); + //this.shiftLayersPane_(); +}; + + +/** + * @inheritDoc + */ +ol.dom.Map.prototype.handleLayerAdd = function(layer) { + goog.base(this, 'handleLayerAdd', layer); +}; + + +/** + * @inheritDoc + */ +ol.dom.Map.prototype.handleLayerRemove = function(layer) { + goog.base(this, 'handleLayerRemove', layer); +}; + + +/** + * @inheritDoc + */ +ol.dom.Map.prototype.handleResolutionChanged = function() { + goog.base(this, 'handleResolutionChanged'); + var resolution = this.getResolution(); + if (resolution != this.renderedResolution_) { + this.resetLayersPane_(); + this.redraw(); + } +}; + + +/** + * @inheritDoc + */ +ol.dom.Map.prototype.handleSizeChanged = function() { + goog.base(this, 'handleSizeChanged'); +}; + + +/** + * @inheritDoc + */ +ol.dom.Map.prototype.redrawInternal = function() { + goog.base(this, 'redrawInternal'); + this.forEachVisibleLayer(function(layer, layerRenderer) { + layerRenderer.redraw(); + }, this); + return false; }; diff --git a/src/ol/dom/tilelayerrenderer.js b/src/ol/dom/tilelayerrenderer.js new file mode 100644 index 0000000000..455f91af71 --- /dev/null +++ b/src/ol/dom/tilelayerrenderer.js @@ -0,0 +1,132 @@ +goog.provide('ol.dom.TileLayerRenderer'); + +goog.require('ol.dom.LayerRenderer'); + + + +/** + * @constructor + * @extends {ol.dom.LayerRenderer} + * @param {ol.dom.Map} map Map. + * @param {ol.TileLayer} tileLayer Tile layer. + * @param {!Element} target Target. + */ +ol.dom.TileLayerRenderer = function(map, tileLayer, target) { + goog.base(this, map, tileLayer, target); + + /** + * @type {Object} + * @private + */ + this.renderedTiles_ = {}; +}; +goog.inherits(ol.dom.TileLayerRenderer, ol.dom.LayerRenderer); + + +/** + * @inheritDoc + */ +ol.dom.TileLayerRenderer.prototype.redraw = function() { + + var map = this.getMap(); + var extent = /** @type {ol.Extent} */ (map.getExtent()); + var resolution = /** @type {number} */ (map.getResolution()); + + var tileLayer = /** @type {ol.TileLayer} */ (this.getLayer()); + var tileStore = tileLayer.getStore(); + var tileGrid = tileStore.getTileGrid(); + var z = tileGrid.getZForResolution(resolution); + + var tileBounds = tileGrid.getExtentTileBounds(z, extent); + var tileSize = tileGrid.getTileSize(); + + var offset = this.getTilesMapOffset_(extent, tileBounds, resolution); + + var fragment = document.createDocumentFragment(); + + var key, tile, x, y, img, newTiles = false; + tileBounds.forEachTileCoord(z, function(tileCoord) { + key = tileCoord.toString(); + tile = this.renderedTiles_[key]; + if (!goog.isDef(tile)) { + tile = tileStore.getTile(tileCoord); + if (goog.isNull(tile)) { + } else { + if (!tile.isLoaded()) { + tile.load(); + } + this.renderedTiles_[key] = tile; + x = tileSize.width * (tileCoord.x - tileBounds.minX); + y = tileSize.height * (tileBounds.maxY - tileCoord.y); + img = tile.getImage(); + img.style.position = 'absolute'; + img.style.top = (y - offset.y) + 'px'; + img.style.left = (x - offset.x) + 'px'; + img.style.width = tileSize.width + 'px'; + img.style.height = tileSize.height + 'px'; + goog.dom.appendChild(fragment, img); + newTiles = true; + } + } + return false; + }, this); + + if (newTiles) { + this.target.appendChild(fragment); + } + + this.removeInvisibleTiles_(tileBounds, z); +}; + + +/** + * Get the pixel offset between top-left corner of tiles and top-left + * corner of map. Return positive values. + * + * @private + * @param {ol.Extent} extent Map extent. + * @param {ol.TileBounds} tileBounds Tile bounds. + * @param {number} resolution Resolution. + * @return {goog.math.Coordinate} Offset. + */ +ol.dom.TileLayerRenderer.prototype.getTilesMapOffset_ = function( + extent, tileBounds, resolution) { + + var tileLayer = /** @type {ol.TileLayer} */ (this.getLayer()); + var tileStore = tileLayer.getStore(); + var tileGrid = tileStore.getTileGrid(); + var z = tileGrid.getZForResolution(resolution); + var tileCoord = new ol.TileCoord(z, tileBounds.minX, tileBounds.maxY); + var tileCoordExtent = tileGrid.getTileCoordExtent(tileCoord); + + var offset = new goog.math.Coordinate( + Math.round((extent.minX - tileCoordExtent.minX) / resolution), + Math.round((tileCoordExtent.maxY - extent.maxY) / resolution)); + + return offset; +}; + + +/** + * Get rid of tiles outside the rendered extent. + * @private + * @param {ol.TileBounds} tileBounds Tile bounds. + * @param {number} z Z. + */ +ol.dom.TileLayerRenderer.prototype.removeInvisibleTiles_ = function( + tileBounds, z) { + var key, tileCoord, prune, tile; + for (key in this.renderedTiles_) { + tileCoord = ol.TileCoord.fromString(key); + prune = z !== tileCoord.z || + tileCoord.x < tileBounds.minX || + tileCoord.x > tileBounds.maxX || + tileCoord.y < tileBounds.minY || + tileCoord.y > tileBounds.maxY; + if (prune) { + tile = this.renderedTiles_[key]; + delete this.renderedTiles_[key]; + this.target.removeChild(tile.getImage()); + } + } +}; diff --git a/src/ol/tilecoord.js b/src/ol/tilecoord.js index 4252ee098e..6e00091d6a 100644 --- a/src/ol/tilecoord.js +++ b/src/ol/tilecoord.js @@ -1,5 +1,6 @@ goog.provide('ol.TileCoord'); +goog.require('goog.array'); goog.require('goog.math.Coordinate'); @@ -46,3 +47,16 @@ ol.TileCoord.prototype.hash = function() { ol.TileCoord.prototype.toString = function() { return [this.z, this.x, this.y].join('/'); }; + + +/** + * @param {string} str String. + * @return {ol.TileCoord} Tile coord. + */ +ol.TileCoord.fromString = function(str) { + var v = str.split('/'); + v = goog.array.map(v, function(e, i, a) { + return parseInt(e); + }); + return new ol.TileCoord(v[0], v[1], v[2]); +}; diff --git a/src/ol/tilecoord_test.js b/src/ol/tilecoord_test.js index d017085fa5..b106a60a81 100644 --- a/src/ol/tilecoord_test.js +++ b/src/ol/tilecoord_test.js @@ -15,3 +15,12 @@ function testHashX() { var tc2 = new ol.TileCoord(3, 1, 1); assertTrue(tc1.hash() != tc2.hash()); } + + +function testFromString() { + var str = '1/2/3'; + var tc = ol.TileCoord.fromString(str); + assertEquals(1, tc.z); + assertEquals(2, tc.x); + assertEquals(3, tc.y); +}