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

View File

@@ -93,11 +93,10 @@ ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) {
/**
* @param {ol.render.EventType} type Event type.
* @param {olx.FrameState} frameState Frame state.
* @param {boolean} wrapX Wrap the x-axis.
* @private
*/
ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ =
function(type, frameState, wrapX) {
function(type, frameState) {
var map = this.getMap();
var context = this.context_;
if (map.hasListener(type)) {
@@ -105,21 +104,29 @@ ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ =
var pixelRatio = frameState.pixelRatio;
var viewState = frameState.viewState;
var projection = viewState.projection;
var projectionExtent = projection.getExtent();
var resolution = viewState.resolution;
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 replayGroup = new ol.render.canvas.ReplayGroup(tolerance,
repeatReplay ?
[projectionExtent[0], extent[1], projectionExtent[2], extent[3]] :
extent,
resolution);
var replayGroup = new ol.render.canvas.ReplayGroup(
tolerance, extent, resolution);
var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio,
extent, transform, rotation);
@@ -129,30 +136,8 @@ ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ =
replayGroup.finish();
if (!replayGroup.isEmpty()) {
replayGroup.replay(context, pixelRatio, transform, rotation,
skippedFeaturesHash);
replayGroup.replay(context, pixelRatio, transform, rotation, {});
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();
this.replayGroup = replayGroup;
@@ -174,7 +159,7 @@ ol.renderer.canvas.Map.prototype.getTransform = function(frameState, offsetX) {
this.canvas_.width / 2, this.canvas_.height / 2,
pixelRatio / resolution, -pixelRatio / resolution,
-viewState.rotation,
-viewState.center[0] + offsetX,
-viewState.center[0] - offsetX,
-viewState.center[1]);
};
@@ -212,11 +197,10 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
this.calculateMatrices2D(frameState);
this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState, false);
this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);
var layerStatesArray = frameState.layerStatesArray;
var viewResolution = frameState.viewState.resolution;
var wrapX = false;
var i, ii, layer, layerRenderer, layerState;
for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
layerState = layerStatesArray[i];
@@ -229,17 +213,12 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
continue;
}
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);
}
}
this.dispatchComposeEvent_(
ol.render.EventType.POSTCOMPOSE, frameState, wrapX);
ol.render.EventType.POSTCOMPOSE, frameState);
if (!this.renderedVisible_) {
goog.style.setElementShown(this.canvas_, true);

View File

@@ -77,6 +77,7 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame =
function(frameState, layerState, context) {
var extent = frameState.extent;
var focus = frameState.focus;
var pixelRatio = frameState.pixelRatio;
var skippedFeatureUids = frameState.skippedFeatureUids;
var viewState = frameState.viewState;
@@ -107,30 +108,47 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame =
// see http://jsperf.com/context-save-restore-versus-variable
var alpha = replayContext.globalAlpha;
replayContext.globalAlpha = layerState.opacity;
replayGroup.replay(
replayContext, pixelRatio, transform, rotation, skippedFeatureUids);
var noSkip = {};
var focusX = focus[0];
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 worldWidth = ol.extent.getWidth(projectionExtent);
var world = 0;
var offsetX;
while (startX < projectionExtent[0]) {
--world;
transform = this.getTransform(frameState, worldWidth * world);
replayGroup.replay(
replayContext, pixelRatio, transform, rotation, skippedFeatureUids);
offsetX = worldWidth * world;
transform = this.getTransform(frameState, offsetX);
replayGroup.replay(replayContext, pixelRatio, transform, rotation,
projLeft + offsetX <= focusX && focusX <= projRight + offsetX ?
skippedFeatureUids : noSkip);
startX += worldWidth;
}
world = 0;
startX = extent[2];
while (startX > projectionExtent[2]) {
++world;
transform = this.getTransform(frameState, worldWidth * world);
replayGroup.replay(
replayContext, pixelRatio, transform, rotation, skippedFeatureUids);
offsetX = worldWidth * world;
transform = this.getTransform(frameState, offsetX);
replayGroup.replay(replayContext, pixelRatio, transform, rotation,
projLeft + offsetX <= focusX && focusX <= projRight + offsetX ?
skippedFeatureUids : noSkip);
startX -= worldWidth;
}
} else {
replayGroup.replay(
replayContext, pixelRatio, transform, rotation, skippedFeatureUids);
}
if (replayContext != context) {

View File

@@ -41,6 +41,7 @@ ol.renderer.Map = function(container, map) {
goog.base(this);
/**
* @private
* @type {ol.Map}
@@ -156,25 +157,20 @@ ol.renderer.Map.prototype.forEachFeatureAtCoordinate =
var projection = viewState.projection;
var translatedX;
var translatedCoordinate = coordinate;
if (projection.canWrapX()) {
var projectionExtent = projection.getExtent();
var worldWidth = ol.extent.getWidth(projectionExtent);
var x = coordinate[0];
if (x < projectionExtent[0] || x > projectionExtent[2]) {
var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
translatedX = x + worldWidth * worldsAway;
translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]];
}
}
if (!goog.isNull(this.replayGroup)) {
result = this.replayGroup.forEachFeatureAtCoordinate(coordinate,
result = this.replayGroup.forEachFeatureAtCoordinate(translatedCoordinate,
viewResolution, viewRotation, {}, forEachFeatureAtCoordinate);
if (!result && goog.isDef(translatedX)) {
result = this.replayGroup.forEachFeatureAtCoordinate(
[translatedX, coordinate[1]],
viewResolution, viewRotation, {}, forEachFeatureAtCoordinate);
}
if (result) {
return result;
}
@@ -189,11 +185,8 @@ ol.renderer.Map.prototype.forEachFeatureAtCoordinate =
layerFilter.call(thisArg2, layer)) {
var layerRenderer = this.getLayerRenderer(layer);
result = layerRenderer.forEachFeatureAtCoordinate(
coordinate, frameState, callback, thisArg);
if (!result && goog.isDef(translatedX)) {
result = layerRenderer.forEachFeatureAtCoordinate(
[translatedX, coordinate[1]], frameState, callback, thisArg);
}
layer.getSource().getWrapX() ? translatedCoordinate : coordinate,
frameState, callback, thisArg);
if (result) {
return result;
}

View File

@@ -24,7 +24,8 @@ ol.source.State = {
* @typedef {{attributions: (Array.<ol.Attribution>|undefined),
* logo: (string|olx.LogoOptions|undefined),
* projection: ol.proj.ProjectionLike,
* state: (ol.source.State|undefined)}}
* state: (ol.source.State|undefined),
* wrapX: (boolean|undefined)}}
*/
ol.source.SourceOptions;
@@ -72,6 +73,12 @@ ol.source.Source = function(options) {
this.state_ = goog.isDef(options.state) ?
options.state : ol.source.State.READY;
/**
* @private
* @type {boolean|undefined}
*/
this.wrapX_ = options.wrapX;
};
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.
* @param {Array.<ol.Attribution>} attributions Attributions.

View File

@@ -47,7 +47,8 @@ ol.source.Tile = function(options) {
extent: options.extent,
logo: options.logo,
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];
/**
* @private
* @type {boolean|undefined}
*/
this.wrapX_ = options.wrapX;
};
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
* is not a global projection, `tileCoord` will be returned unaltered. When
* `this.wrapX_` is true, the tile coordinate will be wrapped horizontally.
* When `this.wrapX_` is `false`, `null` will be returned for tiles that are
* Handles x-axis wrapping. When `wrapX` is `undefined` or the projection is not
* a global projection, `tileCoord` will be returned unaltered. When `wrapX` is
* `true`, the tile coordinate will be wrapped horizontally.
* When `wrapX` is `false`, `null` will be returned for tiles that are
* outside the projection extent.
* @param {ol.TileCoord} tileCoord Tile coordinate.
* @param {ol.proj.Projection=} opt_projection Projection.
@@ -235,8 +230,9 @@ ol.source.Tile.prototype.getWrapXTileCoord =
var projection = goog.isDef(opt_projection) ?
opt_projection : this.getProjection();
var tileGrid = this.getTileGridForProjection(projection);
if (goog.isDef(this.wrapX_) && tileGrid.isGlobal(tileCoord[0], projection)) {
return this.wrapX_ ?
var wrapX = this.getWrapX();
if (goog.isDef(wrapX) && tileGrid.isGlobal(tileCoord[0], projection)) {
return wrapX ?
ol.tilecoord.wrapX(tileCoord, tileGrid, projection) :
ol.tilecoord.clipX(tileCoord, tileGrid, projection);
} else {

View File

@@ -79,7 +79,8 @@ ol.source.Vector = function(opt_options) {
attributions: options.attributions,
logo: options.logo,
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);
}
/**
* @type {boolean}
* @private
*/
this.wrapX_ = goog.isDef(options.wrapX) ? options.wrapX : true;
};
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.
* @private

View File

@@ -19,6 +19,7 @@ describe('ol.renderer.canvas.Map', function() {
beforeEach(function() {
map = new ol.Map({});
map.on('postcompose', function() {});
layer = new ol.layer.Vector({
source: new ol.source.Vector({wrapX: true})
});
@@ -30,16 +31,23 @@ describe('ol.renderer.canvas.Map', function() {
renderer.layerRenderers_[goog.getUid(layer)] = layerRenderer;
});
it('calls #dispatchComposeEvent_() with a wrapX argument', function() {
var spy = sinon.spy(renderer, 'dispatchComposeEvent_');
it('uses correct extent and offset on wrapped worlds', function() {
var spy = sinon.spy(renderer, 'getTransform');
var proj = new ol.proj.Projection({
code: 'foo',
extent: [-180, -90, 180, 90],
global: true
});
var frameState = {
coordinateToPixelMatrix: map.coordinateToPixelMatrix_,
pixelToCoordinateMatrix: map.pixelToCoordinateMatrix_,
pixelRatio: 1,
size: [100, 100],
skippedFeatureUids: {},
extent: proj.getExtent(),
viewState: {
center: [0, 0],
projection: proj,
resolution: 1,
rotation: 0
},
@@ -53,11 +61,21 @@ describe('ol.renderer.canvas.Map', function() {
}],
postRenderFunctions: []
};
frameState.focus = [0, 0];
// focus is on real world
renderer.renderFrame(frameState);
// precompose without wrapX
expect(spy.getCall(0).args[2]).to.be(false);
// postcompose with wrapX
expect(spy.getCall(1).args[2]).to.be(true);
expect(spy.getCall(0).args[1]).to.be(0);
expect(renderer.replayGroup.maxExtent_).to.eql([-180, -90, 180, 90]);
frameState.focus = [-200, 0];
// 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.Map');
goog.require('ol.proj.Projection');
goog.require('ol.renderer.canvas.Layer');
goog.require('ol.renderer.canvas.Map');
goog.require('ol.source.Vector');