diff --git a/externs/proj4js.js b/externs/proj4js.js index 0dd711eb63..ebdf9566da 100644 --- a/externs/proj4js.js +++ b/externs/proj4js.js @@ -70,6 +70,12 @@ Proj4js.Proj.prototype.title; Proj4js.Proj.prototype.units; +/** + * @type {string} + */ +Proj4js.Proj.prototype.srsCode; + + /** * @nosideeffects * @param {Proj4js.Proj} source diff --git a/src/ol/projection.js b/src/ol/projection.js index bf6586e7ee..ad48201ad8 100644 --- a/src/ol/projection.js +++ b/src/ol/projection.js @@ -410,7 +410,12 @@ ol.projection.getProj4jsProjectionFromCode_ = function(code) { var proj4jsProjection = proj4jsProjections[code]; if (!goog.isDef(proj4jsProjection)) { var proj4jsProj = new Proj4js.Proj(code); - proj4jsProjection = new ol.Proj4jsProjection_(code, proj4jsProj); + var srsCode = proj4jsProj.srsCode; + proj4jsProjection = proj4jsProjections[srsCode]; + if (!goog.isDef(proj4jsProjection)) { + proj4jsProjection = new ol.Proj4jsProjection_(srsCode, proj4jsProj); + proj4jsProjections[srsCode] = proj4jsProjection; + } proj4jsProjections[code] = proj4jsProjection; } return proj4jsProjection; diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js index 1374457df6..216625d560 100644 --- a/src/ol/renderer/canvas/canvastilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js @@ -158,8 +158,7 @@ ol.renderer.canvas.TileLayer.prototype.renderFrame = tileState = tile.getState(); if (tileState == ol.TileState.IDLE) { - goog.events.listenOnce(tile, goog.events.EventType.CHANGE, - this.handleTileChange, false, this); + this.listenToTileChange(tile); this.updateWantedTiles(frameState.wantedTiles, tileSource, tileCoord); tileCenter = tileGrid.getTileCoordCenter(tileCoord); frameState.tileQueue.enqueue(tile, tileSourceKey, tileCenter); diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js index afe7463db1..da2dd226a8 100644 --- a/src/ol/renderer/dom/domtilelayerrenderer.js +++ b/src/ol/renderer/dom/domtilelayerrenderer.js @@ -116,8 +116,7 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = tileState = tile.getState(); if (tileState == ol.TileState.IDLE) { - goog.events.listenOnce(tile, goog.events.EventType.CHANGE, - this.handleTileChange, false, this); + this.listenToTileChange(tile); this.updateWantedTiles(frameState.wantedTiles, tileSource, tileCoord); tileCenter = tileGrid.getTileCoordCenter(tileCoord); frameState.tileQueue.enqueue(tile, tileSourceKey, tileCenter); diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index 6a6f1d4aff..a0a8f4b932 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -40,6 +40,12 @@ ol.renderer.Layer = function(mapRenderer, layer) { */ this.layer_ = layer; + /** + * @protected + * @type {Object.} + */ + this.observedTileKeys = {}; + goog.events.listen(this.layer_, ol.Object.getChangedEventType(ol.layer.LayerProperty.BRIGHTNESS), this.handleLayerBrightnessChange, false, this); @@ -167,13 +173,29 @@ ol.renderer.Layer.prototype.handleLayerVisibleChange = function() { /** * Handle changes in tile state. * @param {goog.events.Event} event Tile change event. - * @protected + * @private */ -ol.renderer.Layer.prototype.handleTileChange = function(event) { +ol.renderer.Layer.prototype.handleTileChange_ = function(event) { var tile = /** @type {ol.Tile} */ (event.target); if (tile.getState() === ol.TileState.LOADED) { this.getMap().requestRenderFrame(); } + delete this.observedTileKeys[tile.getKey()]; +}; + + +/** + * Listen once to tileKey, le change event. + * @param {ol.Tile} tile Tile. + * @protected + */ +ol.renderer.Layer.prototype.listenToTileChange = function(tile) { + var tileKey = tile.getKey(); + if (!(tileKey in this.observedTileKeys)) { + this.observedTileKeys[tileKey] = true; + goog.events.listenOnce(tile, goog.events.EventType.CHANGE, + this.handleTileChange_, false, this); + } }; diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index 4059509e0a..0058746bab 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -391,8 +391,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = tileState = tile.getState(); if (tileState == ol.TileState.IDLE) { - goog.events.listenOnce(tile, goog.events.EventType.CHANGE, - this.handleTileChange, false, this); + this.listenToTileChange(tile); this.updateWantedTiles(frameState.wantedTiles, tileSource, tileCoord); tileCenter = tileGrid.getTileCoordCenter(tileCoord); frameState.tileQueue.enqueue(tile, tileSourceKey, tileCenter); diff --git a/src/ol/tile.js b/src/ol/tile.js index c9cf6bed73..6b8956352b 100644 --- a/src/ol/tile.js +++ b/src/ol/tile.js @@ -1,3 +1,5 @@ +// FIXME should inQueue be private? + goog.provide('ol.Tile'); goog.provide('ol.TileState'); @@ -28,6 +30,13 @@ ol.Tile = function(tileCoord) { goog.base(this); + /** + * A count incremented each time the tile is inQueue in a tile queue, + * and decremented each time the tile is dequeued from a tile queue. + * @type {number} + */ + this.inQueue = 0; + /** * @type {ol.TileCoord} */ diff --git a/src/ol/tilequeue.js b/src/ol/tilequeue.js index c5788df972..7e20822308 100644 --- a/src/ol/tilequeue.js +++ b/src/ol/tilequeue.js @@ -89,6 +89,8 @@ ol.TileQueue.prototype.dequeue_ = function() { } var tileKey = tile.getKey(); delete this.queuedTileKeys_[tileKey]; + tile.inQueue--; + goog.asserts.assert(tile.inQueue >= 0); return tile; }; @@ -110,6 +112,8 @@ ol.TileQueue.prototype.enqueue = function(tile, tileSourceKey, tileCenter) { this.heap_.push([priority, tile, tileSourceKey, tileCenter]); this.queuedTileKeys_[tileKey] = true; this.siftDown_(0, this.heap_.length - 1); + tile.inQueue++; + goog.asserts.assert(tile.inQueue > 0); } } }; @@ -246,6 +250,11 @@ ol.TileQueue.prototype.reprioritize = function() { if (priority == ol.TileQueue.DROP) { tileKey = tile.getKey(); delete this.queuedTileKeys_[tileKey]; + tile.inQueue--; + goog.asserts.assert(tile.inQueue >= 0); + if (tile.inQueue === 0) { + goog.events.removeAll(tile); + } } else { node[0] = priority; heap[n++] = node; diff --git a/test/phantom-jasmine/console-runner.js b/test/phantom-jasmine/console-runner.js index fcd4a6d47f..f94de20aa3 100644 --- a/test/phantom-jasmine/console-runner.js +++ b/test/phantom-jasmine/console-runner.js @@ -1,9 +1,9 @@ /** - Jasmine Reporter that outputs test results to the browser console. + Jasmine Reporter that outputs test results to the browser console. Useful for running in a headless environment such as PhantomJs, ZombieJs etc. Usage: - // From your html file that loads jasmine: + // From your html file that loads jasmine: jasmine.getEnv().addReporter(new jasmine.ConsoleReporter()); jasmine.getEnv().execute(); */ @@ -13,17 +13,17 @@ throw "jasmine library isn't loaded!"; } - var ANSI = {} + var ANSI = {}; ANSI.color_map = { "green" : 32, "red" : 31 - } + }; ANSI.colorize_text = function(text, color) { var color_code = this.color_map[color]; return "\033[" + color_code + "m" + text + "\033[0m"; - } - + }; + var ConsoleReporter = function() { if (!console || !console.log) { throw "console isn't present!"; } this.status = this.statuses.stopped; @@ -79,24 +79,81 @@ var resultText = spec.suite.description + " : " + spec.description; this.log(resultText, "red"); - var items = spec.results().getItems() + var items = spec.results().getItems(); for (var i = 0; i < items.length; i++) { var trace = items[i].trace.stack || items[i].trace; this.log(trace, "red"); } }; + /** + * Will hold the title of the current 'group'. + */ + proto.lastTitle = ""; + + /** + * Pads given string up to a target length with a given character on either + * the left or right side. + */ + proto.pad = function(string, len, char, side){ + var str = string + "", + whichSide = side || 'left', + buff = "", + padChar = char || " ", + padded = "", + iterEnd = len - str.length; + + while(buff.length < iterEnd) { + buff += padChar; + } + if (side === 'left') { + padded = buff + str; + } else { + padded = str + buff; + } + // we still need a substring when we are called with e.g. " . " as char. + return padded.substring(0, len); + }; + + /** + * Pads given string up to a target length with a given character on the right + * side. + */ + proto.padRight = function(str, len, char){ + return this.pad(str, len, char, 'right'); + }; + + /** + * Pads given string up to a target length with a given character on the right + * side. + */ + proto.padLeft = function(str, len, char){ + return this.pad(str, len, char, 'left'); + }; + proto.reportSuiteResults = function(suite) { if (!suite.parentSuite) { return; } + // determine title from full name (wo/ own description) + var title = suite.getFullName().replace(new RegExp(suite.description + "$"), ""); + if (this.lastTitle !== title) { + // when title differs, we have a new 'group' + this.log("\n" + title); + } + // always set current title + this.lastTitle = title; + var results = suite.results(); var failed = results.totalCount - results.passedCount; - var color = (failed > 0)? "red" : "green"; - this.log(suite.description + ": " + results.passedCount + " of " + results.totalCount + " passed.", color); + var color = (failed > 0) ? "red" : "green"; + var logStr = " " + this.padRight(suite.description + " ", 60, '.') + + this.padLeft(results.passedCount, 4) + "/" + + this.padRight(results.totalCount, 4) + " ok"; + this.log(logStr, color); }; proto.log = function(str, color) { - var text = (color != undefined)? ANSI.colorize_text(str, color) : str; - console.log(text) + var text = (color)? ANSI.colorize_text(str, color) : str; + console.log(text); }; jasmine.ConsoleReporter = ConsoleReporter; diff --git a/test/spec/ol/projection.test.js b/test/spec/ol/projection.test.js index c961d41819..34e87ca0b3 100644 --- a/test/spec/ol/projection.test.js +++ b/test/spec/ol/projection.test.js @@ -144,6 +144,22 @@ describe('ol.projection', function() { expect(point.y).toRoughlyEqual(200146.976, 1); }); + it('caches the new Proj4js projections given their srsCode', function() { + Proj4js.defs['EPSG:21781'] = + '+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 ' + + '+k_0=1 +x_0=600000 +y_0=200000 +ellps=bessel ' + + '+towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs'; + var code = 'urn:ogc:def:crs:EPSG:21781'; + var srsCode = 'EPSG:21781'; + var proj = ol.projection.getFromCode(code); + expect(ol.projection.proj4jsProjections_.hasOwnProperty(code)) + .toBeTruthy(); + expect(ol.projection.proj4jsProjections_.hasOwnProperty(srsCode)) + .toBeTruthy(); + var proj2 = ol.projection.getFromCode(srsCode); + expect(proj2).toBe(proj); + }); + it('numerically estimates point scale at the equator', function() { var googleProjection = ol.projection.getFromCode('GOOGLE'); expect(googleProjection.getPointResolution(1, new ol.Coordinate(0, 0))). diff --git a/test/spec/ol/tilequeue.test.js b/test/spec/ol/tilequeue.test.js index 3ca38a2981..57db2b2f8c 100644 --- a/test/spec/ol/tilequeue.test.js +++ b/test/spec/ol/tilequeue.test.js @@ -21,13 +21,20 @@ describe('ol.TileQueue', function() { } function addRandomPriorityTiles(tq, num) { + var tiles = []; var i, tile, priority; for (i = 0; i < num; i++) { tile = new ol.Tile(); + tile.toDrop = (i % 2 === 0) ? true : false; + goog.events.listen(tile, goog.events.EventType.CHANGE, + goog.nullFunction); priority = Math.floor(Math.random() * 100); tq.heap_.push([priority, tile, '', new ol.Coordinate(0, 0)]); tq.queuedTileKeys_[tile.getKey()] = true; + tile.inQueue++; + tiles.push(tile); } + return tiles; } describe('heapify', function() { @@ -45,16 +52,15 @@ describe('ol.TileQueue', function() { it('does reprioritize the array', function() { var tq = new ol.TileQueue(function() {}); - addRandomPriorityTiles(tq, 100); + var tiles = addRandomPriorityTiles(tq, 100); tq.heapify_(); // now reprioritize, changing the priority of 50 tiles and removing the // rest - var i = 0; - tq.tilePriorityFunction_ = function() { - if ((i++) % 2 === 0) { + tq.tilePriorityFunction_ = function(tile) { + if (tile.toDrop) { return ol.TileQueue.DROP; } return Math.floor(Math.random() * 100); @@ -64,10 +70,23 @@ describe('ol.TileQueue', function() { expect(tq.heap_.length).toEqual(50); expect(isHeap(tq)).toBeTruthy(); + var i, tile; + for (i = 0; i < tiles.length; ++i) { + tile = tiles[i]; + var hasListener = goog.events.hasListener(tile); + if (tile.toDrop) { + expect(hasListener).toBeFalsy(); + } else { + expect(hasListener).toBeTruthy(); + } + } + }); }); }); +goog.require('goog.events'); +goog.require('goog.events.EventType'); goog.require('ol.Coordinate'); goog.require('ol.Tile'); goog.require('ol.TileQueue');