diff --git a/src/ol/renderer/canvas/canvasrenderer.js b/src/ol/renderer/canvas/canvasrenderer.js index 4899aa5e18..2eda861ddc 100644 --- a/src/ol/renderer/canvas/canvasrenderer.js +++ b/src/ol/renderer/canvas/canvasrenderer.js @@ -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.} 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.} 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.} 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.} 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.} - * @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(); - } -}; - diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 4e900ca5e7..4d2d926f12 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -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 diff --git a/src/ol/renderer/canvas/canvasvectorrenderer.js b/src/ol/renderer/canvas/canvasvectorrenderer.js new file mode 100644 index 0000000000..da7ce21850 --- /dev/null +++ b/src/ol/renderer/canvas/canvasvectorrenderer.js @@ -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.} 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.} 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.} 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.} 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.} + * @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(); + } +}; +