Moving vector rendering to ol.renderer.canvas.VectorRenderer
This commit is contained in:
@@ -1,27 +1,6 @@
|
|||||||
goog.provide('ol.renderer.canvas.Renderer');
|
|
||||||
goog.provide('ol.renderer.canvas.SUPPORTED');
|
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.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.
|
* @type {boolean} Is supported.
|
||||||
*/
|
*/
|
||||||
ol.renderer.canvas.SUPPORTED = ol.canvas.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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ goog.require('ol.filter.LogicalOperator');
|
|||||||
goog.require('ol.geom.GeometryType');
|
goog.require('ol.geom.GeometryType');
|
||||||
goog.require('ol.layer.Vector');
|
goog.require('ol.layer.Vector');
|
||||||
goog.require('ol.renderer.canvas.Layer');
|
goog.require('ol.renderer.canvas.Layer');
|
||||||
goog.require('ol.renderer.canvas.Renderer');
|
goog.require('ol.renderer.canvas.VectorRenderer');
|
||||||
goog.require('ol.tilegrid.TileGrid');
|
goog.require('ol.tilegrid.TileGrid');
|
||||||
|
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
|||||||
sketchCanvas.width = sketchSize.width;
|
sketchCanvas.width = sketchSize.width;
|
||||||
sketchCanvas.height = sketchSize.height;
|
sketchCanvas.height = sketchSize.height;
|
||||||
|
|
||||||
var sketchCanvasRenderer = new ol.renderer.canvas.Renderer(
|
var sketchCanvasRenderer = new ol.renderer.canvas.VectorRenderer(
|
||||||
sketchCanvas, sketchTransform, undefined, this.requestMapRenderFrame_);
|
sketchCanvas, sketchTransform, undefined, this.requestMapRenderFrame_);
|
||||||
|
|
||||||
// clear/resize final canvas
|
// clear/resize final canvas
|
||||||
|
|||||||
440
src/ol/renderer/canvas/canvasvectorrenderer.js
Normal file
440
src/ol/renderer/canvas/canvasvectorrenderer.js
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Reference in New Issue
Block a user