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:
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user