Merge pull request #1389 from twpayne/vector-api-hdpi

[vector-api] High DPI (Retina) support for vector layers
This commit is contained in:
Tom Payne
2013-12-16 03:36:08 -08:00
11 changed files with 105 additions and 39 deletions

View File

@@ -40,7 +40,7 @@ var map = new ol.Map({
target: 'map',
view: new ol.View2D({
center: [0, 0],
zoom: 2
zoom: 1
})
});

View File

@@ -25,6 +25,9 @@
* @typedef {Object} olx.MapOptions
* @property {ol.Collection|Array.<ol.control.Control>|undefined} controls
* Controls initially added to the map.
* @property {number|undefined} devicePixelRatio The ratio between physical
* pixels and device-independent pixels (dips) on the device. If `undefined`
* then it gets set by using `window.devicePixelRatio`.
* @property {ol.Collection|Array.<ol.interaction.Interaction>|undefined} interactions
* Interactions that are initially added to the map.
* @property {Array.<ol.layer.Base>|ol.Collection|undefined} layers Layers.

View File

@@ -20,6 +20,7 @@ goog.require('ol.layer.LayerState');
* @typedef {{animate: boolean,
* attributions: Object.<string, ol.Attribution>,
* coordinateToPixelMatrix: goog.vec.Mat4.Number,
* devicePixelRatio: number,
* extent: (null|ol.Extent),
* focus: ol.Coordinate,
* index: number,

View File

@@ -157,6 +157,13 @@ ol.Map = function(options) {
var optionsInternal = ol.Map.createOptionsInternal(options);
/**
* @private
* @type {number}
*/
this.devicePixelRatio_ = goog.isDef(options.devicePixelRatio) ?
options.devicePixelRatio : ol.BrowserFeature.DEVICE_PIXEL_RATIO;
/**
* @private
* @type {goog.async.AnimationDelay}
@@ -1069,6 +1076,7 @@ ol.Map.prototype.renderFrame_ = function(time) {
animate: false,
attributions: {},
coordinateToPixelMatrix: this.coordinateToPixelMatrix_,
devicePixelRatio: this.devicePixelRatio_,
extent: null,
focus: goog.isNull(this.focus_) ? view2DState.center : this.focus_,
index: this.frameIndex_++,

View File

@@ -17,11 +17,12 @@ goog.require('ol.style.Text');
* @constructor
* @implements {ol.render.IRender}
* @param {CanvasRenderingContext2D} context Context.
* @param {number} pixelRatio Pixel ratio.
* @param {ol.Extent} extent Extent.
* @param {goog.vec.Mat4.AnyType} transform Transform.
* @struct
*/
ol.render.canvas.Immediate = function(context, extent, transform) {
ol.render.canvas.Immediate = function(context, pixelRatio, extent, transform) {
/**
* @private
@@ -29,6 +30,12 @@ ol.render.canvas.Immediate = function(context, extent, transform) {
*/
this.context_ = context;
/**
* @private
* @type {number}
*/
this.pixelRatio_ = pixelRatio;
/**
* @private
* @type {ol.Extent}
@@ -385,8 +392,8 @@ ol.render.canvas.Immediate.prototype.setFillStrokeStyle =
strokeStyle.lineDash : ol.render.canvas.defaultLineDash;
state.lineJoin = goog.isDef(strokeStyle.lineJoin) ?
strokeStyle.lineJoin : ol.render.canvas.defaultLineJoin;
state.lineWidth = goog.isDef(strokeStyle.width) ?
strokeStyle.width : ol.render.canvas.defaultLineWidth;
state.lineWidth = this.pixelRatio_ * (goog.isDef(strokeStyle.width) ?
strokeStyle.width : ol.render.canvas.defaultLineWidth);
state.miterLimit = goog.isDef(strokeStyle.miterLimit) ?
strokeStyle.miterLimit : ol.render.canvas.defaultMiterLimit;
} else {

View File

@@ -41,10 +41,17 @@ ol.render.canvas.Instruction = {
/**
* @constructor
* @implements {ol.render.IRender}
* @param {number} pixelRatio Pixel ratio.
* @protected
* @struct
*/
ol.render.canvas.Replay = function() {
ol.render.canvas.Replay = function(pixelRatio) {
/**
* @protected
* @type {number}
*/
this.pixelRatio = pixelRatio;
/**
* @private
@@ -430,12 +437,13 @@ ol.render.canvas.Replay.prototype.setTextStyle = goog.abstractMethod;
/**
* @constructor
* @extends {ol.render.canvas.Replay}
* @param {number} pixelRatio Pixel ratio.
* @protected
* @struct
*/
ol.render.canvas.ImageReplay = function() {
ol.render.canvas.ImageReplay = function(pixelRatio) {
goog.base(this);
goog.base(this, pixelRatio);
/**
* @private
@@ -588,12 +596,13 @@ ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) {
/**
* @constructor
* @extends {ol.render.canvas.Replay}
* @param {number} pixelRatio Pixel ratio.
* @protected
* @struct
*/
ol.render.canvas.LineStringReplay = function() {
ol.render.canvas.LineStringReplay = function(pixelRatio) {
goog.base(this);
goog.base(this, pixelRatio);
/**
* @private
@@ -779,8 +788,8 @@ ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle =
strokeStyle.lineDash : ol.render.canvas.defaultLineDash;
this.state_.lineJoin = goog.isDef(strokeStyle.lineJoin) ?
strokeStyle.lineJoin : ol.render.canvas.defaultLineJoin;
this.state_.lineWidth = goog.isDef(strokeStyle.width) ?
strokeStyle.width : ol.render.canvas.defaultLineWidth;
this.state_.lineWidth = this.pixelRatio * (goog.isDef(strokeStyle.width) ?
strokeStyle.width : ol.render.canvas.defaultLineWidth);
this.state_.miterLimit = goog.isDef(strokeStyle.miterLimit) ?
strokeStyle.miterLimit : ol.render.canvas.defaultMiterLimit;
};
@@ -790,12 +799,13 @@ ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle =
/**
* @constructor
* @extends {ol.render.canvas.Replay}
* @param {number} pixelRatio Pixel ratio.
* @protected
* @struct
*/
ol.render.canvas.PolygonReplay = function() {
ol.render.canvas.PolygonReplay = function(pixelRatio) {
goog.base(this);
goog.base(this, pixelRatio);
/**
* @private
@@ -990,8 +1000,8 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle =
strokeStyle.lineDash : ol.render.canvas.defaultLineDash;
state.lineJoin = goog.isDef(strokeStyle.lineJoin) ?
strokeStyle.lineJoin : ol.render.canvas.defaultLineJoin;
state.lineWidth = goog.isDef(strokeStyle.width) ?
strokeStyle.width : ol.render.canvas.defaultLineWidth;
state.lineWidth = this.pixelRatio * (goog.isDef(strokeStyle.width) ?
strokeStyle.width : ol.render.canvas.defaultLineWidth);
state.miterLimit = goog.isDef(strokeStyle.miterLimit) ?
strokeStyle.miterLimit : ol.render.canvas.defaultMiterLimit;
} else {
@@ -1052,9 +1062,16 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() {
/**
* @constructor
* @implements {ol.render.IReplayGroup}
* @param {number} pixelRatio Pixel ratio.
* @struct
*/
ol.render.canvas.ReplayGroup = function() {
ol.render.canvas.ReplayGroup = function(pixelRatio) {
/**
* @private
* @type {number}
*/
this.pixelRatio_ = pixelRatio;
/**
* @private
@@ -1248,7 +1265,7 @@ ol.render.canvas.ReplayGroup.prototype.getReplay =
if (!goog.isDef(replay)) {
var constructor = ol.render.canvas.BATCH_CONSTRUCTORS_[replayType];
goog.asserts.assert(goog.isDef(constructor));
replay = new constructor();
replay = new constructor(this.pixelRatio_);
replayes[replayType] = replay;
}
return replay;
@@ -1266,7 +1283,8 @@ ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
/**
* @const
* @private
* @type {Object.<ol.render.ReplayType, function(new: ol.render.canvas.Replay)>}
* @type {Object.<ol.render.ReplayType,
* function(new: ol.render.canvas.Replay, number)>}
*/
ol.render.canvas.BATCH_CONSTRUCTORS_ = {
'Image': ol.render.canvas.ImageReplay,

View File

@@ -96,9 +96,12 @@ ol.renderer.canvas.ImageLayer.prototype.prepareFrame =
image = this.image_;
var imageExtent = image.getExtent();
var imageResolution = image.getResolution();
var devicePixelRatio = frameState.devicePixelRatio;
ol.vec.Mat4.makeTransform2D(this.imageTransform_,
frameState.size[0] / 2, frameState.size[1] / 2,
imageResolution / viewResolution, imageResolution / viewResolution,
devicePixelRatio * frameState.size[0] / 2,
devicePixelRatio * frameState.size[1] / 2,
devicePixelRatio * imageResolution / viewResolution,
devicePixelRatio * imageResolution / viewResolution,
viewRotation,
(imageExtent[0] - viewCenter[0]) / imageResolution,
(viewCenter[1] - imageExtent[3]) / imageResolution);

View File

@@ -85,8 +85,8 @@ ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ =
if (layer.hasListener(type)) {
var transform = goog.isDef(opt_transform) ?
opt_transform : this.getTransform(frameState);
var render = new ol.render.canvas.Immediate(context, frameState.extent,
transform);
var render = new ol.render.canvas.Immediate(
context, frameState.devicePixelRatio, frameState.extent, transform);
var composeEvent = new ol.render.Event(type, layer, render, frameState,
context, null);
layer.dispatchEvent(composeEvent);
@@ -139,9 +139,12 @@ ol.renderer.canvas.Layer.prototype.getImageTransform = goog.abstractMethod;
*/
ol.renderer.canvas.Layer.prototype.getTransform = function(frameState) {
var view2DState = frameState.view2DState;
var devicePixelRatio = frameState.devicePixelRatio;
return ol.vec.Mat4.makeTransform2D(this.transform_,
frameState.size[0] / 2, frameState.size[1] / 2,
1 / view2DState.resolution, -1 / view2DState.resolution,
devicePixelRatio * frameState.size[0] / 2,
devicePixelRatio * frameState.size[1] / 2,
devicePixelRatio / view2DState.resolution,
-devicePixelRatio / view2DState.resolution,
-view2DState.rotation,
-view2DState.center[0], -view2DState.center[1]);
};

View File

@@ -6,6 +6,7 @@ goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.style');
goog.require('goog.vec.Mat4');
goog.require('ol.css');
goog.require('ol.layer.Image');
goog.require('ol.layer.Tile');
@@ -19,6 +20,7 @@ goog.require('ol.renderer.canvas.Layer');
goog.require('ol.renderer.canvas.TileLayer');
goog.require('ol.renderer.canvas.VectorLayer');
goog.require('ol.source.State');
goog.require('ol.vec.Mat4');
@@ -56,6 +58,12 @@ ol.renderer.canvas.Map = function(container, map) {
this.context_ = /** @type {CanvasRenderingContext2D} */
(this.canvas_.getContext('2d'));
/**
* @private
* @type {!goog.vec.Mat4.Number}
*/
this.transform_ = goog.vec.Mat4.createNumber();
};
goog.inherits(ol.renderer.canvas.Map, ol.renderer.Map);
@@ -87,8 +95,17 @@ ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ =
var map = this.getMap();
var context = this.context_;
if (map.hasListener(type)) {
var view2DState = frameState.view2DState;
var devicePixelRatio = frameState.devicePixelRatio;
ol.vec.Mat4.makeTransform2D(this.transform_,
this.canvas_.width / 2,
this.canvas_.height / 2,
devicePixelRatio / view2DState.resolution,
-devicePixelRatio / view2DState.resolution,
-view2DState.rotation,
-view2DState.center[0], -view2DState.center[1]);
var render = new ol.render.canvas.Immediate(
context, frameState.extent, frameState.coordinateToPixelMatrix);
context, devicePixelRatio, frameState.extent, this.transform_);
var composeEvent = new ol.render.Event(type, map, render, frameState,
context, null);
map.dispatchEvent(composeEvent);
@@ -121,11 +138,12 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
}
var context = this.context_;
var size = frameState.size;
if (this.canvas_.width != size[0] || this.canvas_.height != size[1]) {
this.canvas_.width = size[0];
this.canvas_.height = size[1];
var ratio = frameState.devicePixelRatio;
var width = frameState.size[0] * ratio;
var height = frameState.size[1] * ratio;
if (this.canvas_.width != width || this.canvas_.height != height) {
this.canvas_.width = width;
this.canvas_.height = height;
} else {
context.clearRect(0, 0, this.canvas_.width, this.canvas_.height);
}

View File

@@ -382,10 +382,12 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame =
this.scheduleExpireCache(frameState, tileSource);
this.updateLogos(frameState, tileSource);
var devicePixelRatio = frameState.devicePixelRatio;
ol.vec.Mat4.makeTransform2D(this.imageTransform_,
frameState.size[0] / 2, frameState.size[1] / 2,
tileResolution / view2DState.resolution,
tileResolution / view2DState.resolution,
devicePixelRatio * frameState.size[0] / 2,
devicePixelRatio * frameState.size[1] / 2,
devicePixelRatio * tileResolution / view2DState.resolution,
devicePixelRatio * tileResolution / view2DState.resolution,
view2DState.rotation,
(origin[0] - center[0]) / tileResolution,
(center[1] - origin[1]) / tileResolution);

View File

@@ -172,6 +172,7 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame =
var vectorSource = vectorLayer.getVectorSource();
var frameStateExtent = frameState.extent;
var frameStateResolution = frameState.view2DState.resolution;
var pixelRatio = frameState.devicePixelRatio;
if (!this.dirty_ &&
this.renderedResolution_ == frameStateResolution &&
@@ -198,15 +199,15 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame =
if (!goog.isDef(styleFunction)) {
styleFunction = ol.layer.Vector.defaultStyleFunction;
}
var replayGroup = new ol.render.canvas.ReplayGroup();
var replayGroup = new ol.render.canvas.ReplayGroup(pixelRatio);
vectorSource.forEachFeatureInExtent(extent,
/**
* @param {ol.Feature} feature Feature.
*/
function(feature) {
this.dirty_ = this.dirty_ ||
this.renderFeature(feature, frameStateResolution, styleFunction,
replayGroup);
this.renderFeature(feature, frameStateResolution, pixelRatio,
styleFunction, replayGroup);
}, this);
replayGroup.finish();
@@ -223,20 +224,22 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame =
/**
* @param {ol.Feature} feature Feature.
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
* @param {ol.style.StyleFunction} styleFunction Style function.
* @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
* @return {boolean} `true` if an image is loading.
*/
ol.renderer.canvas.VectorLayer.prototype.renderFeature =
function(feature, resolution, styleFunction, replayGroup) {
function(feature, resolution, pixelRatio, styleFunction, replayGroup) {
var loading = false;
var styles = styleFunction(feature, resolution);
// FIXME if styles is null, should we use the default style?
if (!goog.isDefAndNotNull(styles)) {
return false;
}
// simplify to a tolerance of half a CSS pixel
var squaredTolerance = resolution * resolution / 4;
// simplify to a tolerance of half a device pixel
var squaredTolerance =
resolution * resolution / (4 * pixelRatio * pixelRatio);
var i, ii, style, imageStyle, imageState;
for (i = 0, ii = styles.length; i < ii; ++i) {
style = styles[i];