Render map replay group on the correct world instead of wrapping it

By using the frameState's focus, we can adjust extent and transform and
render it for the world of interest instead of wrapping it and rendering
for every visible world.
This commit is contained in:
Andreas Hocevar
2015-04-22 09:11:19 +02:00
parent 23ed120361
commit 513677fecd
7 changed files with 108 additions and 101 deletions
+23 -44
View File
@@ -93,11 +93,10 @@ ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) {
/** /**
* @param {ol.render.EventType} type Event type. * @param {ol.render.EventType} type Event type.
* @param {olx.FrameState} frameState Frame state. * @param {olx.FrameState} frameState Frame state.
* @param {boolean} wrapX Wrap the x-axis.
* @private * @private
*/ */
ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ = ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ =
function(type, frameState, wrapX) { function(type, frameState) {
var map = this.getMap(); var map = this.getMap();
var context = this.context_; var context = this.context_;
if (map.hasListener(type)) { if (map.hasListener(type)) {
@@ -105,21 +104,29 @@ ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ =
var pixelRatio = frameState.pixelRatio; var pixelRatio = frameState.pixelRatio;
var viewState = frameState.viewState; var viewState = frameState.viewState;
var projection = viewState.projection; var projection = viewState.projection;
var projectionExtent = projection.getExtent();
var resolution = viewState.resolution; var resolution = viewState.resolution;
var rotation = viewState.rotation; var rotation = viewState.rotation;
var repeatReplay = (wrapX && projection.canWrapX() &&
!ol.extent.containsExtent(projectionExtent, extent));
var skippedFeaturesHash = {};
var transform = this.getTransform(frameState, 0); var offsetX = 0;
if (projection.canWrapX()) {
var projectionExtent = projection.getExtent();
var worldWidth = ol.extent.getWidth(projectionExtent);
var x = frameState.focus[0];
if (x < projectionExtent[0] || x > projectionExtent[2]) {
var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
offsetX = worldWidth * worldsAway;
extent = [
extent[0] + offsetX, extent[1],
extent[2] + offsetX, extent[3]
];
}
}
var transform = this.getTransform(frameState, offsetX);
var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio); var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
var replayGroup = new ol.render.canvas.ReplayGroup(tolerance, var replayGroup = new ol.render.canvas.ReplayGroup(
repeatReplay ? tolerance, extent, resolution);
[projectionExtent[0], extent[1], projectionExtent[2], extent[3]] :
extent,
resolution);
var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio, var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio,
extent, transform, rotation); extent, transform, rotation);
@@ -129,30 +136,8 @@ ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ =
replayGroup.finish(); replayGroup.finish();
if (!replayGroup.isEmpty()) { if (!replayGroup.isEmpty()) {
replayGroup.replay(context, pixelRatio, transform, rotation, replayGroup.replay(context, pixelRatio, transform, rotation, {});
skippedFeaturesHash);
if (repeatReplay) {
var startX = extent[0];
var worldWidth = ol.extent.getWidth(projectionExtent);
var world = 0;
while (startX < projectionExtent[0]) {
--world;
transform = this.getTransform(frameState, worldWidth * world);
replayGroup.replay(context, pixelRatio, transform, rotation,
skippedFeaturesHash);
startX += worldWidth;
}
world = 0;
startX = extent[2];
while (startX > projectionExtent[2]) {
++world;
transform = this.getTransform(frameState, worldWidth * ++world);
replayGroup.replay(context, pixelRatio, transform, rotation,
skippedFeaturesHash);
startX -= worldWidth;
}
}
} }
vectorContext.flush(); vectorContext.flush();
this.replayGroup = replayGroup; this.replayGroup = replayGroup;
@@ -174,7 +159,7 @@ ol.renderer.canvas.Map.prototype.getTransform = function(frameState, offsetX) {
this.canvas_.width / 2, this.canvas_.height / 2, this.canvas_.width / 2, this.canvas_.height / 2,
pixelRatio / resolution, -pixelRatio / resolution, pixelRatio / resolution, -pixelRatio / resolution,
-viewState.rotation, -viewState.rotation,
-viewState.center[0] + offsetX, -viewState.center[0] - offsetX,
-viewState.center[1]); -viewState.center[1]);
}; };
@@ -212,11 +197,10 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
this.calculateMatrices2D(frameState); this.calculateMatrices2D(frameState);
this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState, false); this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
var layerStatesArray = frameState.layerStatesArray; var layerStatesArray = frameState.layerStatesArray;
var viewResolution = frameState.viewState.resolution; var viewResolution = frameState.viewState.resolution;
var wrapX = false;
var i, ii, layer, layerRenderer, layerState; var i, ii, layer, layerRenderer, layerState;
for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
layerState = layerStatesArray[i]; layerState = layerStatesArray[i];
@@ -229,17 +213,12 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
continue; continue;
} }
if (layerRenderer.prepareFrame(frameState, layerState)) { if (layerRenderer.prepareFrame(frameState, layerState)) {
// As soon as a vector layer on the map has wrapX set to true, we make
// feature overlays wrap the x-axis too.
if (layer instanceof ol.layer.Vector && layer.getSource().getWrapX()) {
wrapX = true;
}
layerRenderer.composeFrame(frameState, layerState, context); layerRenderer.composeFrame(frameState, layerState, context);
} }
} }
this.dispatchComposeEvent_( this.dispatchComposeEvent_(
ol.render.EventType.POSTCOMPOSE, frameState, wrapX); ol.render.EventType.POSTCOMPOSE, frameState);
if (!this.renderedVisible_) { if (!this.renderedVisible_) {
goog.style.setElementShown(this.canvas_, true); goog.style.setElementShown(this.canvas_, true);
@@ -77,6 +77,7 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame =
function(frameState, layerState, context) { function(frameState, layerState, context) {
var extent = frameState.extent; var extent = frameState.extent;
var focus = frameState.focus;
var pixelRatio = frameState.pixelRatio; var pixelRatio = frameState.pixelRatio;
var skippedFeatureUids = frameState.skippedFeatureUids; var skippedFeatureUids = frameState.skippedFeatureUids;
var viewState = frameState.viewState; var viewState = frameState.viewState;
@@ -107,30 +108,47 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame =
// see http://jsperf.com/context-save-restore-versus-variable // see http://jsperf.com/context-save-restore-versus-variable
var alpha = replayContext.globalAlpha; var alpha = replayContext.globalAlpha;
replayContext.globalAlpha = layerState.opacity; replayContext.globalAlpha = layerState.opacity;
replayGroup.replay( var noSkip = {};
replayContext, pixelRatio, transform, rotation, skippedFeatureUids); var focusX = focus[0];
if (vectorSource.getWrapX() && projection.canWrapX() && if (vectorSource.getWrapX() && projection.canWrapX() &&
!ol.extent.containsExtent(projectionExtent, frameState.extent)) { !ol.extent.containsExtent(projectionExtent, extent)) {
var projLeft = projectionExtent[0];
var projRight = projectionExtent[2];
// A feature from skippedFeatureUids will only be skipped in the world
// that has the frameState's focus, because this is where a feature
// overlay for highlighting or selection would render the skipped
// feature.
replayGroup.replay(replayContext, pixelRatio, transform, rotation,
projLeft <= focusX && focusX <= projRight ?
skippedFeatureUids : noSkip);
var startX = extent[0]; var startX = extent[0];
var worldWidth = ol.extent.getWidth(projectionExtent); var worldWidth = ol.extent.getWidth(projectionExtent);
var world = 0; var world = 0;
var offsetX;
while (startX < projectionExtent[0]) { while (startX < projectionExtent[0]) {
--world; --world;
transform = this.getTransform(frameState, worldWidth * world); offsetX = worldWidth * world;
replayGroup.replay( transform = this.getTransform(frameState, offsetX);
replayContext, pixelRatio, transform, rotation, skippedFeatureUids); replayGroup.replay(replayContext, pixelRatio, transform, rotation,
projLeft + offsetX <= focusX && focusX <= projRight + offsetX ?
skippedFeatureUids : noSkip);
startX += worldWidth; startX += worldWidth;
} }
world = 0; world = 0;
startX = extent[2]; startX = extent[2];
while (startX > projectionExtent[2]) { while (startX > projectionExtent[2]) {
++world; ++world;
transform = this.getTransform(frameState, worldWidth * world); offsetX = worldWidth * world;
replayGroup.replay( transform = this.getTransform(frameState, offsetX);
replayContext, pixelRatio, transform, rotation, skippedFeatureUids); replayGroup.replay(replayContext, pixelRatio, transform, rotation,
projLeft + offsetX <= focusX && focusX <= projRight + offsetX ?
skippedFeatureUids : noSkip);
startX -= worldWidth; startX -= worldWidth;
} }
} else {
replayGroup.replay(
replayContext, pixelRatio, transform, rotation, skippedFeatureUids);
} }
if (replayContext != context) { if (replayContext != context) {
+6 -13
View File
@@ -41,6 +41,7 @@ ol.renderer.Map = function(container, map) {
goog.base(this); goog.base(this);
/** /**
* @private * @private
* @type {ol.Map} * @type {ol.Map}
@@ -156,25 +157,20 @@ ol.renderer.Map.prototype.forEachFeatureAtCoordinate =
var projection = viewState.projection; var projection = viewState.projection;
var translatedX; var translatedCoordinate = coordinate;
if (projection.canWrapX()) { if (projection.canWrapX()) {
var projectionExtent = projection.getExtent(); var projectionExtent = projection.getExtent();
var worldWidth = ol.extent.getWidth(projectionExtent); var worldWidth = ol.extent.getWidth(projectionExtent);
var x = coordinate[0]; var x = coordinate[0];
if (x < projectionExtent[0] || x > projectionExtent[2]) { if (x < projectionExtent[0] || x > projectionExtent[2]) {
var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
translatedX = x + worldWidth * worldsAway; translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]];
} }
} }
if (!goog.isNull(this.replayGroup)) { if (!goog.isNull(this.replayGroup)) {
result = this.replayGroup.forEachFeatureAtCoordinate(coordinate, result = this.replayGroup.forEachFeatureAtCoordinate(translatedCoordinate,
viewResolution, viewRotation, {}, forEachFeatureAtCoordinate); viewResolution, viewRotation, {}, forEachFeatureAtCoordinate);
if (!result && goog.isDef(translatedX)) {
result = this.replayGroup.forEachFeatureAtCoordinate(
[translatedX, coordinate[1]],
viewResolution, viewRotation, {}, forEachFeatureAtCoordinate);
}
if (result) { if (result) {
return result; return result;
} }
@@ -189,11 +185,8 @@ ol.renderer.Map.prototype.forEachFeatureAtCoordinate =
layerFilter.call(thisArg2, layer)) { layerFilter.call(thisArg2, layer)) {
var layerRenderer = this.getLayerRenderer(layer); var layerRenderer = this.getLayerRenderer(layer);
result = layerRenderer.forEachFeatureAtCoordinate( result = layerRenderer.forEachFeatureAtCoordinate(
coordinate, frameState, callback, thisArg); layer.getSource().getWrapX() ? translatedCoordinate : coordinate,
if (!result && goog.isDef(translatedX)) { frameState, callback, thisArg);
result = layerRenderer.forEachFeatureAtCoordinate(
[translatedX, coordinate[1]], frameState, callback, thisArg);
}
if (result) { if (result) {
return result; return result;
} }
+16 -1
View File
@@ -24,7 +24,8 @@ ol.source.State = {
* @typedef {{attributions: (Array.<ol.Attribution>|undefined), * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
* logo: (string|olx.LogoOptions|undefined), * logo: (string|olx.LogoOptions|undefined),
* projection: ol.proj.ProjectionLike, * projection: ol.proj.ProjectionLike,
* state: (ol.source.State|undefined)}} * state: (ol.source.State|undefined),
* wrapX: (boolean|undefined)}}
*/ */
ol.source.SourceOptions; ol.source.SourceOptions;
@@ -72,6 +73,12 @@ ol.source.Source = function(options) {
this.state_ = goog.isDef(options.state) ? this.state_ = goog.isDef(options.state) ?
options.state : ol.source.State.READY; options.state : ol.source.State.READY;
/**
* @private
* @type {boolean|undefined}
*/
this.wrapX_ = options.wrapX;
}; };
goog.inherits(ol.source.Source, ol.Object); goog.inherits(ol.source.Source, ol.Object);
@@ -135,6 +142,14 @@ ol.source.Source.prototype.getState = function() {
}; };
/**
* @return {boolean|undefined} Wrap X.
*/
ol.source.Source.prototype.getWrapX = function() {
return this.wrapX_;
};
/** /**
* Set the attributions of the source. * Set the attributions of the source.
* @param {Array.<ol.Attribution>} attributions Attributions. * @param {Array.<ol.Attribution>} attributions Attributions.
+9 -13
View File
@@ -47,7 +47,8 @@ ol.source.Tile = function(options) {
extent: options.extent, extent: options.extent,
logo: options.logo, logo: options.logo,
projection: options.projection, projection: options.projection,
state: options.state state: options.state,
wrapX: options.wrapX
}); });
/** /**
@@ -81,12 +82,6 @@ ol.source.Tile = function(options) {
*/ */
this.tmpSize = [0, 0]; this.tmpSize = [0, 0];
/**
* @private
* @type {boolean|undefined}
*/
this.wrapX_ = options.wrapX;
}; };
goog.inherits(ol.source.Tile, ol.source.Source); goog.inherits(ol.source.Tile, ol.source.Source);
@@ -221,10 +216,10 @@ ol.source.Tile.prototype.getTilePixelSize =
/** /**
* Handles x-axis wrapping. When `this.wrapX_` is undefined or the projection * Handles x-axis wrapping. When `wrapX` is `undefined` or the projection is not
* is not a global projection, `tileCoord` will be returned unaltered. When * a global projection, `tileCoord` will be returned unaltered. When `wrapX` is
* `this.wrapX_` is true, the tile coordinate will be wrapped horizontally. * `true`, the tile coordinate will be wrapped horizontally.
* When `this.wrapX_` is `false`, `null` will be returned for tiles that are * When `wrapX` is `false`, `null` will be returned for tiles that are
* outside the projection extent. * outside the projection extent.
* @param {ol.TileCoord} tileCoord Tile coordinate. * @param {ol.TileCoord} tileCoord Tile coordinate.
* @param {ol.proj.Projection=} opt_projection Projection. * @param {ol.proj.Projection=} opt_projection Projection.
@@ -235,8 +230,9 @@ ol.source.Tile.prototype.getWrapXTileCoord =
var projection = goog.isDef(opt_projection) ? var projection = goog.isDef(opt_projection) ?
opt_projection : this.getProjection(); opt_projection : this.getProjection();
var tileGrid = this.getTileGridForProjection(projection); var tileGrid = this.getTileGridForProjection(projection);
if (goog.isDef(this.wrapX_) && tileGrid.isGlobal(tileCoord[0], projection)) { var wrapX = this.getWrapX();
return this.wrapX_ ? if (goog.isDef(wrapX) && tileGrid.isGlobal(tileCoord[0], projection)) {
return wrapX ?
ol.tilecoord.wrapX(tileCoord, tileGrid, projection) : ol.tilecoord.wrapX(tileCoord, tileGrid, projection) :
ol.tilecoord.clipX(tileCoord, tileGrid, projection); ol.tilecoord.clipX(tileCoord, tileGrid, projection);
} else { } else {
+2 -15
View File
@@ -79,7 +79,8 @@ ol.source.Vector = function(opt_options) {
attributions: options.attributions, attributions: options.attributions,
logo: options.logo, logo: options.logo,
projection: undefined, projection: undefined,
state: ol.source.State.READY state: ol.source.State.READY,
wrapX: goog.isDef(options.wrapX) ? options.wrapX : true
}); });
/** /**
@@ -146,12 +147,6 @@ ol.source.Vector = function(opt_options) {
this.addFeaturesInternal(options.features); this.addFeaturesInternal(options.features);
} }
/**
* @type {boolean}
* @private
*/
this.wrapX_ = goog.isDef(options.wrapX) ? options.wrapX : true;
}; };
goog.inherits(ol.source.Vector, ol.source.Source); goog.inherits(ol.source.Vector, ol.source.Source);
@@ -570,14 +565,6 @@ ol.source.Vector.prototype.getFeatureById = function(id) {
}; };
/**
* @return {boolean}
*/
ol.source.Vector.prototype.getWrapX = function() {
return this.wrapX_;
};
/** /**
* @param {goog.events.Event} event Event. * @param {goog.events.Event} event Event.
* @private * @private
@@ -19,6 +19,7 @@ describe('ol.renderer.canvas.Map', function() {
beforeEach(function() { beforeEach(function() {
map = new ol.Map({}); map = new ol.Map({});
map.on('postcompose', function() {});
layer = new ol.layer.Vector({ layer = new ol.layer.Vector({
source: new ol.source.Vector({wrapX: true}) source: new ol.source.Vector({wrapX: true})
}); });
@@ -30,16 +31,23 @@ describe('ol.renderer.canvas.Map', function() {
renderer.layerRenderers_[goog.getUid(layer)] = layerRenderer; renderer.layerRenderers_[goog.getUid(layer)] = layerRenderer;
}); });
it('calls #dispatchComposeEvent_() with a wrapX argument', function() { it('uses correct extent and offset on wrapped worlds', function() {
var spy = sinon.spy(renderer, 'dispatchComposeEvent_'); var spy = sinon.spy(renderer, 'getTransform');
var proj = new ol.proj.Projection({
code: 'foo',
extent: [-180, -90, 180, 90],
global: true
});
var frameState = { var frameState = {
coordinateToPixelMatrix: map.coordinateToPixelMatrix_, coordinateToPixelMatrix: map.coordinateToPixelMatrix_,
pixelToCoordinateMatrix: map.pixelToCoordinateMatrix_, pixelToCoordinateMatrix: map.pixelToCoordinateMatrix_,
pixelRatio: 1, pixelRatio: 1,
size: [100, 100], size: [100, 100],
skippedFeatureUids: {}, skippedFeatureUids: {},
extent: proj.getExtent(),
viewState: { viewState: {
center: [0, 0], center: [0, 0],
projection: proj,
resolution: 1, resolution: 1,
rotation: 0 rotation: 0
}, },
@@ -53,11 +61,21 @@ describe('ol.renderer.canvas.Map', function() {
}], }],
postRenderFunctions: [] postRenderFunctions: []
}; };
frameState.focus = [0, 0];
// focus is on real world
renderer.renderFrame(frameState); renderer.renderFrame(frameState);
// precompose without wrapX expect(spy.getCall(0).args[1]).to.be(0);
expect(spy.getCall(0).args[2]).to.be(false); expect(renderer.replayGroup.maxExtent_).to.eql([-180, -90, 180, 90]);
// postcompose with wrapX frameState.focus = [-200, 0];
expect(spy.getCall(1).args[2]).to.be(true); // focus is one world left of the real world
renderer.renderFrame(frameState);
expect(spy.getCall(1).args[1]).to.be(360);
expect(renderer.replayGroup.maxExtent_).to.eql([180, -90, 540, 90]);
frameState.focus = [200, 0];
// focus is one world right of the real world
renderer.renderFrame(frameState);
expect(spy.getCall(2).args[1]).to.be(-360);
expect(renderer.replayGroup.maxExtent_).to.eql([-540, -90, -180, 90]);
}); });
}); });
@@ -66,6 +84,7 @@ describe('ol.renderer.canvas.Map', function() {
goog.require('ol.layer.Vector'); goog.require('ol.layer.Vector');
goog.require('ol.Map'); goog.require('ol.Map');
goog.require('ol.proj.Projection');
goog.require('ol.renderer.canvas.Layer'); goog.require('ol.renderer.canvas.Layer');
goog.require('ol.renderer.canvas.Map'); goog.require('ol.renderer.canvas.Map');
goog.require('ol.source.Vector'); goog.require('ol.source.Vector');