Moving vector rendering to ol.renderer.canvas.VectorRenderer

This commit is contained in:
ahocevar
2013-03-12 10:05:47 +01:00
parent bb8755eb68
commit eeecd424bf
3 changed files with 442 additions and 440 deletions

View File

@@ -1,27 +1,6 @@
goog.provide('ol.renderer.canvas.Renderer');
goog.provide('ol.renderer.canvas.SUPPORTED');
goog.require('goog.asserts');
goog.require('goog.net.ImageLoader');
goog.require('goog.vec.Mat4');
goog.require('ol.Feature');
goog.require('ol.Pixel');
goog.require('ol.canvas');
goog.require('ol.geom.Geometry');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.style.IconLiteral');
goog.require('ol.style.LineLiteral');
goog.require('ol.style.PointLiteral');
goog.require('ol.style.PolygonLiteral');
goog.require('ol.style.ShapeLiteral');
goog.require('ol.style.ShapeType');
goog.require('ol.style.SymbolizerLiteral');
/**
@@ -29,420 +8,3 @@ goog.require('ol.style.SymbolizerLiteral');
* @type {boolean} Is supported.
*/
ol.renderer.canvas.SUPPORTED = ol.canvas.SUPPORTED;
/**
* @constructor
* @param {HTMLCanvasElement} canvas Target canvas.
* @param {goog.vec.Mat4.Number} transform Transform.
* @param {ol.Pixel=} opt_offset Pixel offset for top-left corner. This is
* provided as an optional argument as a convenience in cases where the
* transform applies to a separate canvas.
* @param {function()=} opt_iconLoadedCallback Callback for deferred rendering
* when images need to be loaded before rendering.
*/
ol.renderer.canvas.Renderer =
function(canvas, transform, opt_offset, opt_iconLoadedCallback) {
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d')),
dx = goog.isDef(opt_offset) ? opt_offset.x : 0,
dy = goog.isDef(opt_offset) ? opt_offset.y : 0;
/**
* @type {goog.vec.Mat4.Number}
* @private
*/
this.transform_ = transform;
context.setTransform(
goog.vec.Mat4.getElement(transform, 0, 0),
goog.vec.Mat4.getElement(transform, 1, 0),
goog.vec.Mat4.getElement(transform, 0, 1),
goog.vec.Mat4.getElement(transform, 1, 1),
goog.vec.Mat4.getElement(transform, 0, 3) + dx,
goog.vec.Mat4.getElement(transform, 1, 3) + dy);
var vec = [1, 0, 0];
goog.vec.Mat4.multVec3NoTranslate(transform, vec, vec);
/**
* @type {number}
* @private
*/
this.inverseScale_ = 1 / Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]);
/**
* @type {CanvasRenderingContext2D}
* @private
*/
this.context_ = context;
/**
* @type {function()|undefined}
* @private
*/
this.iconLoadedCallback_ = opt_iconLoadedCallback;
};
/**
* @param {ol.geom.GeometryType} type Geometry type.
* @param {Array.<ol.Feature>} features Array of features.
* @param {ol.style.SymbolizerLiteral} symbolizer Symbolizer.
* @return {boolean} true if deferred, false if rendered.
*/
ol.renderer.canvas.Renderer.prototype.renderFeaturesByGeometryType =
function(type, features, symbolizer) {
var deferred = false;
switch (type) {
case ol.geom.GeometryType.POINT:
case ol.geom.GeometryType.MULTIPOINT:
goog.asserts.assert(symbolizer instanceof ol.style.PointLiteral,
'Expected point symbolizer: ' + symbolizer);
deferred = this.renderPointFeatures_(
features, /** @type {ol.style.PointLiteral} */ (symbolizer));
break;
case ol.geom.GeometryType.LINESTRING:
case ol.geom.GeometryType.MULTILINESTRING:
goog.asserts.assert(symbolizer instanceof ol.style.LineLiteral,
'Expected line symbolizer: ' + symbolizer);
this.renderLineStringFeatures_(
features, /** @type {ol.style.LineLiteral} */ (symbolizer));
break;
case ol.geom.GeometryType.POLYGON:
case ol.geom.GeometryType.MULTIPOLYGON:
goog.asserts.assert(symbolizer instanceof ol.style.PolygonLiteral,
'Expected polygon symbolizer: ' + symbolizer);
this.renderPolygonFeatures_(
features, /** @type {ol.style.PolygonLiteral} */ (symbolizer));
break;
default:
throw new Error('Rendering not implemented for geometry type: ' + type);
}
return deferred;
};
/**
* @param {Array.<ol.Feature>} features Array of line features.
* @param {ol.style.LineLiteral} symbolizer Line symbolizer.
* @private
*/
ol.renderer.canvas.Renderer.prototype.renderLineStringFeatures_ =
function(features, symbolizer) {
var context = this.context_,
i, ii, geometry, components, j, jj, line, dim, k, kk, x, y;
context.globalAlpha = symbolizer.opacity;
context.strokeStyle = symbolizer.strokeColor;
context.lineWidth = symbolizer.strokeWidth * this.inverseScale_;
context.lineCap = 'round'; // TODO: accept this as a symbolizer property
context.lineJoin = 'round'; // TODO: accept this as a symbolizer property
context.beginPath();
for (i = 0, ii = features.length; i < ii; ++i) {
geometry = features[i].getGeometry();
if (geometry instanceof ol.geom.LineString) {
components = [geometry];
} else {
goog.asserts.assert(geometry instanceof ol.geom.MultiLineString,
'Expected MultiLineString');
components = geometry.components;
}
for (j = 0, jj = components.length; j < jj; ++j) {
line = components[j];
dim = line.dimension;
for (k = 0, kk = line.getCount(); k < kk; ++k) {
x = line.get(k, 0);
y = line.get(k, 1);
if (k === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
}
}
context.stroke();
};
/**
* @param {Array.<ol.Feature>} features Array of point features.
* @param {ol.style.PointLiteral} symbolizer Point symbolizer.
* @return {boolean} true if deferred, false if rendered.
* @private
*/
ol.renderer.canvas.Renderer.prototype.renderPointFeatures_ =
function(features, symbolizer) {
var context = this.context_,
content, alpha, i, ii, geometry, components, j, jj, point, vec;
if (symbolizer instanceof ol.style.ShapeLiteral) {
content = ol.renderer.canvas.Renderer.renderShape(symbolizer);
alpha = 1;
} else if (symbolizer instanceof ol.style.IconLiteral) {
content = ol.renderer.canvas.Renderer.renderIcon(
symbolizer, this.iconLoadedCallback_);
alpha = symbolizer.opacity;
} else {
throw new Error('Unsupported symbolizer: ' + symbolizer);
}
if (goog.isNull(content)) {
return true;
}
var midWidth = content.width / 2;
var midHeight = content.height / 2;
context.save();
context.setTransform(1, 0, 0, 1, -midWidth, -midHeight);
context.globalAlpha = alpha;
for (i = 0, ii = features.length; i < ii; ++i) {
geometry = features[i].getGeometry();
if (geometry instanceof ol.geom.Point) {
components = [geometry];
} else {
goog.asserts.assert(geometry instanceof ol.geom.MultiPoint,
'Expected MultiPoint');
components = geometry.components;
}
for (j = 0, jj = components.length; j < jj; ++j) {
point = components[j];
vec = goog.vec.Mat4.multVec3(
this.transform_, [point.get(0), point.get(1), 0], []);
context.drawImage(content, vec[0], vec[1], content.width, content.height);
}
}
context.restore();
return false;
};
/**
* @param {Array.<ol.Feature>} features Array of polygon features.
* @param {ol.style.PolygonLiteral} symbolizer Polygon symbolizer.
* @private
*/
ol.renderer.canvas.Renderer.prototype.renderPolygonFeatures_ =
function(features, symbolizer) {
var context = this.context_,
strokeColor = symbolizer.strokeColor,
fillColor = symbolizer.fillColor,
i, ii, geometry, components, j, jj, poly,
rings, numRings, ring, dim, k, kk, x, y;
context.globalAlpha = symbolizer.opacity;
if (strokeColor) {
context.strokeStyle = symbolizer.strokeColor;
context.lineWidth = symbolizer.strokeWidth * this.inverseScale_;
context.lineCap = 'round'; // TODO: accept this as a symbolizer property
context.lineJoin = 'round'; // TODO: accept this as a symbolizer property
}
if (fillColor) {
context.fillStyle = fillColor;
}
/**
* Four scenarios covered here:
* 1) stroke only, no holes - only need to have a single path
* 2) fill only, no holes - only need to have a single path
* 3) fill and stroke, no holes
* 4) holes - render polygon to sketch canvas first
*/
context.beginPath();
for (i = 0, ii = features.length; i < ii; ++i) {
geometry = features[i].getGeometry();
if (geometry instanceof ol.geom.Polygon) {
components = [geometry];
} else {
goog.asserts.assert(geometry instanceof ol.geom.MultiPolygon,
'Expected MultiPolygon');
components = geometry.components;
}
for (j = 0, jj = components.length; j < jj; ++j) {
poly = components[j];
dim = poly.dimension;
rings = poly.rings;
numRings = rings.length;
if (numRings > 0) {
// TODO: scenario 4
ring = rings[0];
for (k = 0, kk = ring.getCount(); k < kk; ++k) {
x = ring.get(k, 0);
y = ring.get(k, 1);
if (k === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
if (fillColor && strokeColor) {
// scenario 3 - fill and stroke each time
context.fill();
context.stroke();
if (i < ii - 1 || j < jj - 1) {
context.beginPath();
}
}
}
}
}
if (!(fillColor && strokeColor)) {
if (fillColor) {
// scenario 2 - fill all at once
context.fill();
} else {
// scenario 1 - stroke all at once
context.stroke();
}
}
};
/**
* @param {ol.style.ShapeLiteral} circle Shape symbolizer.
* @return {!HTMLCanvasElement} Canvas element.
* @private
*/
ol.renderer.canvas.Renderer.renderCircle_ = function(circle) {
var strokeWidth = circle.strokeWidth || 0,
size = circle.size + (2 * strokeWidth) + 1,
mid = size / 2,
canvas = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS)),
context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d')),
fillColor = circle.fillColor,
strokeColor = circle.strokeColor,
twoPi = Math.PI * 2;
canvas.height = size;
canvas.width = size;
context.globalAlpha = circle.opacity;
if (fillColor) {
context.fillStyle = fillColor;
}
if (strokeColor) {
context.lineWidth = strokeWidth;
context.strokeStyle = strokeColor;
context.lineCap = 'round'; // TODO: accept this as a symbolizer property
context.lineJoin = 'round'; // TODO: accept this as a symbolizer property
}
context.beginPath();
context.arc(mid, mid, circle.size / 2, 0, twoPi, true);
if (fillColor) {
context.fill();
}
if (strokeColor) {
context.stroke();
}
return canvas;
};
/**
* @param {ol.style.ShapeLiteral} shape Shape symbolizer.
* @return {!HTMLCanvasElement} Canvas element.
*/
ol.renderer.canvas.Renderer.renderShape = function(shape) {
var canvas;
if (shape.type === ol.style.ShapeType.CIRCLE) {
canvas = ol.renderer.canvas.Renderer.renderCircle_(shape);
} else {
throw new Error('Unsupported shape type: ' + shape);
}
return canvas;
};
/**
* @param {ol.style.IconLiteral} icon Icon literal.
* @param {function()=} opt_callback Callback which will be called when
* the icon is loaded and rendering will work without deferring.
* @return {HTMLImageElement} image element of null if deferred.
*/
ol.renderer.canvas.Renderer.renderIcon = function(icon, opt_callback) {
var url = icon.url;
var image = ol.renderer.canvas.Renderer.icons_[url];
var deferred = false;
if (!goog.isDef(image)) {
deferred = true;
image = /** @type {HTMLImageElement} */
(goog.dom.createElement(goog.dom.TagName.IMG));
goog.events.listenOnce(image, goog.events.EventType.ERROR,
goog.bind(ol.renderer.canvas.Renderer.handleIconError_, null,
opt_callback),
false, ol.renderer.canvas.Renderer.renderIcon);
goog.events.listenOnce(image, goog.events.EventType.LOAD,
goog.bind(ol.renderer.canvas.Renderer.handleIconLoad_, null,
opt_callback),
false, ol.renderer.canvas.Renderer.renderIcon);
image.setAttribute('src', url);
ol.renderer.canvas.Renderer.icons_[url] = image;
} else if (!goog.isNull(image)) {
var width = icon.width,
height = icon.height;
if (goog.isDef(width) && goog.isDef(height)) {
image.width = width;
image.height = height;
} else if (goog.isDef(width)) {
image.height = width / image.width * image.height;
image.width = width;
} else if (goog.isDef(height)) {
image.width = height / image.height * image.width;
image.height = height;
}
}
return deferred ? null : image;
};
/**
* @type {Object.<string, HTMLImageElement>}
* @private
*/
ol.renderer.canvas.Renderer.icons_ = {};
/**
* @param {function()=} opt_callback Callback.
* @param {Event=} opt_event Event.
* @private
*/
ol.renderer.canvas.Renderer.handleIconError_ =
function(opt_callback, opt_event) {
if (goog.isDef(opt_event)) {
var url = opt_event.target.getAttribute('src');
ol.renderer.canvas.Renderer.icons_[url] = null;
ol.renderer.canvas.Renderer.handleIconLoad_(opt_callback, opt_event);
}
};
/**
* @param {function()=} opt_callback Callback.
* @param {Event=} opt_event Event.
* @private
*/
ol.renderer.canvas.Renderer.handleIconLoad_ =
function(opt_callback, opt_event) {
if (goog.isDef(opt_event)) {
var url = opt_event.target.getAttribute('src');
ol.renderer.canvas.Renderer.icons_[url] =
/** @type {HTMLImageElement} */ (opt_event.target);
}
if (goog.isDef(opt_callback)) {
opt_callback();
}
};

View File

@@ -13,7 +13,7 @@ goog.require('ol.filter.LogicalOperator');
goog.require('ol.geom.GeometryType');
goog.require('ol.layer.Vector');
goog.require('ol.renderer.canvas.Layer');
goog.require('ol.renderer.canvas.Renderer');
goog.require('ol.renderer.canvas.VectorRenderer');
goog.require('ol.tilegrid.TileGrid');
@@ -290,7 +290,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
sketchCanvas.width = sketchSize.width;
sketchCanvas.height = sketchSize.height;
var sketchCanvasRenderer = new ol.renderer.canvas.Renderer(
var sketchCanvasRenderer = new ol.renderer.canvas.VectorRenderer(
sketchCanvas, sketchTransform, undefined, this.requestMapRenderFrame_);
// clear/resize final canvas

View File

@@ -0,0 +1,440 @@
goog.provide('ol.renderer.canvas.VectorRenderer');
goog.require('goog.asserts');
goog.require('goog.net.ImageLoader');
goog.require('goog.vec.Mat4');
goog.require('ol.Feature');
goog.require('ol.Pixel');
goog.require('ol.canvas');
goog.require('ol.geom.Geometry');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.style.IconLiteral');
goog.require('ol.style.LineLiteral');
goog.require('ol.style.PointLiteral');
goog.require('ol.style.PolygonLiteral');
goog.require('ol.style.ShapeLiteral');
goog.require('ol.style.ShapeType');
goog.require('ol.style.SymbolizerLiteral');
/**
* @constructor
* @param {HTMLCanvasElement} canvas Target canvas.
* @param {goog.vec.Mat4.Number} transform Transform.
* @param {ol.Pixel=} opt_offset Pixel offset for top-left corner. This is
* provided as an optional argument as a convenience in cases where the
* transform applies to a separate canvas.
* @param {function()=} opt_iconLoadedCallback Callback for deferred rendering
* when images need to be loaded before rendering.
*/
ol.renderer.canvas.VectorRenderer =
function(canvas, transform, opt_offset, opt_iconLoadedCallback) {
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d')),
dx = goog.isDef(opt_offset) ? opt_offset.x : 0,
dy = goog.isDef(opt_offset) ? opt_offset.y : 0;
/**
* @type {goog.vec.Mat4.Number}
* @private
*/
this.transform_ = transform;
context.setTransform(
goog.vec.Mat4.getElement(transform, 0, 0),
goog.vec.Mat4.getElement(transform, 1, 0),
goog.vec.Mat4.getElement(transform, 0, 1),
goog.vec.Mat4.getElement(transform, 1, 1),
goog.vec.Mat4.getElement(transform, 0, 3) + dx,
goog.vec.Mat4.getElement(transform, 1, 3) + dy);
var vec = [1, 0, 0];
goog.vec.Mat4.multVec3NoTranslate(transform, vec, vec);
/**
* @type {number}
* @private
*/
this.inverseScale_ = 1 / Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]);
/**
* @type {CanvasRenderingContext2D}
* @private
*/
this.context_ = context;
/**
* @type {function()|undefined}
* @private
*/
this.iconLoadedCallback_ = opt_iconLoadedCallback;
};
/**
* @param {ol.geom.GeometryType} type Geometry type.
* @param {Array.<ol.Feature>} features Array of features.
* @param {ol.style.SymbolizerLiteral} symbolizer Symbolizer.
* @return {boolean} true if deferred, false if rendered.
*/
ol.renderer.canvas.VectorRenderer.prototype.renderFeaturesByGeometryType =
function(type, features, symbolizer) {
var deferred = false;
switch (type) {
case ol.geom.GeometryType.POINT:
case ol.geom.GeometryType.MULTIPOINT:
goog.asserts.assert(symbolizer instanceof ol.style.PointLiteral,
'Expected point symbolizer: ' + symbolizer);
deferred = this.renderPointFeatures_(
features, /** @type {ol.style.PointLiteral} */ (symbolizer));
break;
case ol.geom.GeometryType.LINESTRING:
case ol.geom.GeometryType.MULTILINESTRING:
goog.asserts.assert(symbolizer instanceof ol.style.LineLiteral,
'Expected line symbolizer: ' + symbolizer);
this.renderLineStringFeatures_(
features, /** @type {ol.style.LineLiteral} */ (symbolizer));
break;
case ol.geom.GeometryType.POLYGON:
case ol.geom.GeometryType.MULTIPOLYGON:
goog.asserts.assert(symbolizer instanceof ol.style.PolygonLiteral,
'Expected polygon symbolizer: ' + symbolizer);
this.renderPolygonFeatures_(
features, /** @type {ol.style.PolygonLiteral} */ (symbolizer));
break;
default:
throw new Error('Rendering not implemented for geometry type: ' + type);
}
return deferred;
};
/**
* @param {Array.<ol.Feature>} features Array of line features.
* @param {ol.style.LineLiteral} symbolizer Line symbolizer.
* @private
*/
ol.renderer.canvas.VectorRenderer.prototype.renderLineStringFeatures_ =
function(features, symbolizer) {
var context = this.context_,
i, ii, geometry, components, j, jj, line, dim, k, kk, x, y;
context.globalAlpha = symbolizer.opacity;
context.strokeStyle = symbolizer.strokeColor;
context.lineWidth = symbolizer.strokeWidth * this.inverseScale_;
context.lineCap = 'round'; // TODO: accept this as a symbolizer property
context.lineJoin = 'round'; // TODO: accept this as a symbolizer property
context.beginPath();
for (i = 0, ii = features.length; i < ii; ++i) {
geometry = features[i].getGeometry();
if (geometry instanceof ol.geom.LineString) {
components = [geometry];
} else {
goog.asserts.assert(geometry instanceof ol.geom.MultiLineString,
'Expected MultiLineString');
components = geometry.components;
}
for (j = 0, jj = components.length; j < jj; ++j) {
line = components[j];
dim = line.dimension;
for (k = 0, kk = line.getCount(); k < kk; ++k) {
x = line.get(k, 0);
y = line.get(k, 1);
if (k === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
}
}
context.stroke();
};
/**
* @param {Array.<ol.Feature>} features Array of point features.
* @param {ol.style.PointLiteral} symbolizer Point symbolizer.
* @return {boolean} true if deferred, false if rendered.
* @private
*/
ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ =
function(features, symbolizer) {
var context = this.context_,
content, alpha, i, ii, geometry, components, j, jj, point, vec;
if (symbolizer instanceof ol.style.ShapeLiteral) {
content = ol.renderer.canvas.VectorRenderer.renderShape(symbolizer);
alpha = 1;
} else if (symbolizer instanceof ol.style.IconLiteral) {
content = ol.renderer.canvas.VectorRenderer.renderIcon(
symbolizer, this.iconLoadedCallback_);
alpha = symbolizer.opacity;
} else {
throw new Error('Unsupported symbolizer: ' + symbolizer);
}
if (goog.isNull(content)) {
return true;
}
var midWidth = content.width / 2;
var midHeight = content.height / 2;
context.save();
context.setTransform(1, 0, 0, 1, -midWidth, -midHeight);
context.globalAlpha = alpha;
for (i = 0, ii = features.length; i < ii; ++i) {
geometry = features[i].getGeometry();
if (geometry instanceof ol.geom.Point) {
components = [geometry];
} else {
goog.asserts.assert(geometry instanceof ol.geom.MultiPoint,
'Expected MultiPoint');
components = geometry.components;
}
for (j = 0, jj = components.length; j < jj; ++j) {
point = components[j];
vec = goog.vec.Mat4.multVec3(
this.transform_, [point.get(0), point.get(1), 0], []);
context.drawImage(content, vec[0], vec[1], content.width, content.height);
}
}
context.restore();
return false;
};
/**
* @param {Array.<ol.Feature>} features Array of polygon features.
* @param {ol.style.PolygonLiteral} symbolizer Polygon symbolizer.
* @private
*/
ol.renderer.canvas.VectorRenderer.prototype.renderPolygonFeatures_ =
function(features, symbolizer) {
var context = this.context_,
strokeColor = symbolizer.strokeColor,
fillColor = symbolizer.fillColor,
i, ii, geometry, components, j, jj, poly,
rings, numRings, ring, dim, k, kk, x, y;
context.globalAlpha = symbolizer.opacity;
if (strokeColor) {
context.strokeStyle = symbolizer.strokeColor;
context.lineWidth = symbolizer.strokeWidth * this.inverseScale_;
context.lineCap = 'round'; // TODO: accept this as a symbolizer property
context.lineJoin = 'round'; // TODO: accept this as a symbolizer property
}
if (fillColor) {
context.fillStyle = fillColor;
}
/**
* Four scenarios covered here:
* 1) stroke only, no holes - only need to have a single path
* 2) fill only, no holes - only need to have a single path
* 3) fill and stroke, no holes
* 4) holes - render polygon to sketch canvas first
*/
context.beginPath();
for (i = 0, ii = features.length; i < ii; ++i) {
geometry = features[i].getGeometry();
if (geometry instanceof ol.geom.Polygon) {
components = [geometry];
} else {
goog.asserts.assert(geometry instanceof ol.geom.MultiPolygon,
'Expected MultiPolygon');
components = geometry.components;
}
for (j = 0, jj = components.length; j < jj; ++j) {
poly = components[j];
dim = poly.dimension;
rings = poly.rings;
numRings = rings.length;
if (numRings > 0) {
// TODO: scenario 4
ring = rings[0];
for (k = 0, kk = ring.getCount(); k < kk; ++k) {
x = ring.get(k, 0);
y = ring.get(k, 1);
if (k === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
if (fillColor && strokeColor) {
// scenario 3 - fill and stroke each time
context.fill();
context.stroke();
if (i < ii - 1 || j < jj - 1) {
context.beginPath();
}
}
}
}
}
if (!(fillColor && strokeColor)) {
if (fillColor) {
// scenario 2 - fill all at once
context.fill();
} else {
// scenario 1 - stroke all at once
context.stroke();
}
}
};
/**
* @param {ol.style.ShapeLiteral} circle Shape symbolizer.
* @return {!HTMLCanvasElement} Canvas element.
* @private
*/
ol.renderer.canvas.VectorRenderer.renderCircle_ = function(circle) {
var strokeWidth = circle.strokeWidth || 0,
size = circle.size + (2 * strokeWidth) + 1,
mid = size / 2,
canvas = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS)),
context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d')),
fillColor = circle.fillColor,
strokeColor = circle.strokeColor,
twoPi = Math.PI * 2;
canvas.height = size;
canvas.width = size;
context.globalAlpha = circle.opacity;
if (fillColor) {
context.fillStyle = fillColor;
}
if (strokeColor) {
context.lineWidth = strokeWidth;
context.strokeStyle = strokeColor;
context.lineCap = 'round'; // TODO: accept this as a symbolizer property
context.lineJoin = 'round'; // TODO: accept this as a symbolizer property
}
context.beginPath();
context.arc(mid, mid, circle.size / 2, 0, twoPi, true);
if (fillColor) {
context.fill();
}
if (strokeColor) {
context.stroke();
}
return canvas;
};
/**
* @param {ol.style.ShapeLiteral} shape Shape symbolizer.
* @return {!HTMLCanvasElement} Canvas element.
*/
ol.renderer.canvas.VectorRenderer.renderShape = function(shape) {
var canvas;
if (shape.type === ol.style.ShapeType.CIRCLE) {
canvas = ol.renderer.canvas.VectorRenderer.renderCircle_(shape);
} else {
throw new Error('Unsupported shape type: ' + shape);
}
return canvas;
};
/**
* @param {ol.style.IconLiteral} icon Icon literal.
* @param {function()=} opt_callback Callback which will be called when
* the icon is loaded and rendering will work without deferring.
* @return {HTMLImageElement} image element of null if deferred.
*/
ol.renderer.canvas.VectorRenderer.renderIcon = function(icon, opt_callback) {
var url = icon.url;
var image = ol.renderer.canvas.VectorRenderer.icons_[url];
var deferred = false;
if (!goog.isDef(image)) {
deferred = true;
image = /** @type {HTMLImageElement} */
(goog.dom.createElement(goog.dom.TagName.IMG));
goog.events.listenOnce(image, goog.events.EventType.ERROR,
goog.bind(ol.renderer.canvas.VectorRenderer.handleIconError_, null,
opt_callback),
false, ol.renderer.canvas.VectorRenderer.renderIcon);
goog.events.listenOnce(image, goog.events.EventType.LOAD,
goog.bind(ol.renderer.canvas.VectorRenderer.handleIconLoad_, null,
opt_callback),
false, ol.renderer.canvas.VectorRenderer.renderIcon);
image.setAttribute('src', url);
ol.renderer.canvas.VectorRenderer.icons_[url] = image;
} else if (!goog.isNull(image)) {
var width = icon.width,
height = icon.height;
if (goog.isDef(width) && goog.isDef(height)) {
image.width = width;
image.height = height;
} else if (goog.isDef(width)) {
image.height = width / image.width * image.height;
image.width = width;
} else if (goog.isDef(height)) {
image.width = height / image.height * image.width;
image.height = height;
}
}
return deferred ? null : image;
};
/**
* @type {Object.<string, HTMLImageElement>}
* @private
*/
ol.renderer.canvas.VectorRenderer.icons_ = {};
/**
* @param {function()=} opt_callback Callback.
* @param {Event=} opt_event Event.
* @private
*/
ol.renderer.canvas.VectorRenderer.handleIconError_ =
function(opt_callback, opt_event) {
if (goog.isDef(opt_event)) {
var url = opt_event.target.getAttribute('src');
ol.renderer.canvas.VectorRenderer.icons_[url] = null;
ol.renderer.canvas.VectorRenderer.handleIconLoad_(opt_callback, opt_event);
}
};
/**
* @param {function()=} opt_callback Callback.
* @param {Event=} opt_event Event.
* @private
*/
ol.renderer.canvas.VectorRenderer.handleIconLoad_ =
function(opt_callback, opt_event) {
if (goog.isDef(opt_event)) {
var url = opt_event.target.getAttribute('src');
ol.renderer.canvas.VectorRenderer.icons_[url] =
/** @type {HTMLImageElement} */ (opt_event.target);
}
if (goog.isDef(opt_callback)) {
opt_callback();
}
};