Rendering icons from external graphic urls
This commit is contained in:
@@ -2,6 +2,7 @@ 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');
|
||||
@@ -11,6 +12,7 @@ goog.require('ol.geom.GeometryType');
|
||||
goog.require('ol.geom.LineString');
|
||||
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');
|
||||
@@ -34,8 +36,11 @@ ol.renderer.canvas.SUPPORTED = ol.canvas.SUPPORTED;
|
||||
* @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) {
|
||||
ol.renderer.canvas.Renderer =
|
||||
function(canvas, transform, opt_offset, opt_iconLoadedCallback) {
|
||||
|
||||
var context = /** @type {CanvasRenderingContext2D} */
|
||||
(canvas.getContext('2d')),
|
||||
@@ -70,6 +75,12 @@ ol.renderer.canvas.Renderer = function(canvas, transform, opt_offset) {
|
||||
*/
|
||||
this.context_ = context;
|
||||
|
||||
/**
|
||||
* @type {function()|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.iconLoadedCallback_ = opt_iconLoadedCallback;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -77,13 +88,15 @@ ol.renderer.canvas.Renderer = function(canvas, transform, opt_offset) {
|
||||
* @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:
|
||||
goog.asserts.assert(symbolizer instanceof ol.style.PointLiteral);
|
||||
this.renderPointFeatures_(
|
||||
deferred = this.renderPointFeatures_(
|
||||
features, /** @type {ol.style.PointLiteral} */ (symbolizer));
|
||||
break;
|
||||
case ol.geom.GeometryType.LINESTRING:
|
||||
@@ -99,6 +112,7 @@ ol.renderer.canvas.Renderer.prototype.renderFeaturesByGeometryType =
|
||||
default:
|
||||
throw new Error('Rendering not implemented for geometry type: ' + type);
|
||||
}
|
||||
return deferred;
|
||||
};
|
||||
|
||||
|
||||
@@ -138,31 +152,44 @@ ol.renderer.canvas.Renderer.prototype.renderLineStringFeatures_ =
|
||||
/**
|
||||
* @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_,
|
||||
canvas, i, ii, point, vec;
|
||||
content, alpha, i, ii, point, vec;
|
||||
|
||||
if (symbolizer instanceof ol.style.ShapeLiteral) {
|
||||
canvas = ol.renderer.canvas.Renderer.renderShape(symbolizer);
|
||||
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);
|
||||
}
|
||||
|
||||
var mid = canvas.width / 2;
|
||||
if (goog.isNull(content)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var midWidth = content.width / 2;
|
||||
var midHeight = content.height / 2;
|
||||
context.save();
|
||||
context.setTransform(1, 0, 0, 1, -mid, -mid);
|
||||
context.globalAlpha = 1;
|
||||
context.setTransform(1, 0, 0, 1, -midWidth, -midHeight);
|
||||
context.globalAlpha = alpha;
|
||||
for (i = 0, ii = features.length; i < ii; ++i) {
|
||||
point = /** @type {ol.geom.Point} */ features[i].getGeometry();
|
||||
vec = goog.vec.Mat4.multVec3(
|
||||
this.transform_, [point.get(0), point.get(1), 0], []);
|
||||
context.drawImage(canvas, vec[0], vec[1]);
|
||||
context.drawImage(content, vec[0], vec[1]);
|
||||
}
|
||||
context.restore();
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -293,3 +320,84 @@ ol.renderer.canvas.Renderer.renderShape = function(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;
|
||||
} else if (goog.isDef(height)) {
|
||||
image.width = height / image.height * image.width;
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -126,6 +126,15 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) {
|
||||
*/
|
||||
this.tileGrid_ = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {function()}
|
||||
*/
|
||||
this.requestMapRenderFrame_ = goog.bind(function() {
|
||||
this.dirty_ = true;
|
||||
mapRenderer.getMap().requestRenderFrame();
|
||||
}, this);
|
||||
|
||||
};
|
||||
goog.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);
|
||||
|
||||
@@ -252,7 +261,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
||||
sketchCanvas.height = sketchSize.height;
|
||||
|
||||
var sketchCanvasRenderer = new ol.renderer.canvas.Renderer(
|
||||
sketchCanvas, sketchTransform);
|
||||
sketchCanvas, sketchTransform, undefined, this.requestMapRenderFrame_);
|
||||
|
||||
// clear/resize final canvas
|
||||
var finalCanvas = this.canvas_;
|
||||
@@ -267,15 +276,15 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
||||
var filters = this.geometryFilters_,
|
||||
numFilters = filters.length,
|
||||
i, geomFilter, extentFilter, type, features,
|
||||
groups, group, j, numGroups;
|
||||
groups, group, j, numGroups, deferred;
|
||||
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
||||
for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
||||
deferred = false;
|
||||
tileCoord = new ol.TileCoord(z, x, y);
|
||||
key = tileCoord.toString();
|
||||
if (this.tileCache_.containsKey(key)) {
|
||||
tilesToRender[key] = tileCoord;
|
||||
} else if (!frameState.viewHints[ol.ViewHint.ANIMATING]) {
|
||||
tilesToRender[key] = tileCoord;
|
||||
extentFilter = new ol.filter.Extent(
|
||||
tileGrid.getTileCoordExtent(tileCoord));
|
||||
for (i = 0; i < numFilters; ++i) {
|
||||
@@ -288,11 +297,14 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
||||
numGroups = groups.length;
|
||||
for (j = 0; j < numGroups; ++j) {
|
||||
group = groups[j];
|
||||
sketchCanvasRenderer.renderFeaturesByGeometryType(type,
|
||||
group[0], group[1]);
|
||||
deferred = sketchCanvasRenderer.renderFeaturesByGeometryType(
|
||||
type, group[0], group[1]) || deferred;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!deferred) {
|
||||
tilesToRender[key] = tileCoord;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
170
src/ol/style/icon.js
Normal file
170
src/ol/style/icon.js
Normal file
@@ -0,0 +1,170 @@
|
||||
goog.provide('ol.style.Icon');
|
||||
goog.provide('ol.style.IconLiteral');
|
||||
goog.provide('ol.style.IconType');
|
||||
|
||||
goog.require('ol.Expression');
|
||||
goog.require('ol.ExpressionLiteral');
|
||||
goog.require('ol.style.Point');
|
||||
goog.require('ol.style.PointLiteral');
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{url: (string),
|
||||
* width: (number|undefined),
|
||||
* height: (number|undefined),
|
||||
* opacity: (number),
|
||||
* rotation: (number)}}
|
||||
*/
|
||||
ol.style.IconLiteralOptions;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {ol.style.PointLiteral}
|
||||
* @param {ol.style.IconLiteralOptions} config Symbolizer properties.
|
||||
*/
|
||||
ol.style.IconLiteral = function(config) {
|
||||
|
||||
/** @type {string} */
|
||||
this.url = config.url;
|
||||
|
||||
/** @type {number|undefined} */
|
||||
this.width = config.width;
|
||||
|
||||
/** @type {number|undefined} */
|
||||
this.height = config.height;
|
||||
|
||||
/** @type {number} */
|
||||
this.opacity = config.opacity;
|
||||
|
||||
/** @type {number} */
|
||||
this.rotation = config.rotation;
|
||||
|
||||
};
|
||||
goog.inherits(ol.style.IconLiteral, ol.style.PointLiteral);
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.style.IconLiteral.prototype.equals = function(iconLiteral) {
|
||||
return this.url == iconLiteral.type &&
|
||||
this.width == iconLiteral.width &&
|
||||
this.height == iconLiteral.height &&
|
||||
this.opacity == iconLiteral.opacity &&
|
||||
this.rotation == iconLiteral.rotation;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{url: (string|ol.Expression),
|
||||
* width: (number|ol.Expression|undefined),
|
||||
* height: (number|ol.Expression|undefined),
|
||||
* opacity: (number|ol.Expression|undefined),
|
||||
* rotation: (number|ol.Expression|undefined)}}
|
||||
*/
|
||||
ol.style.IconOptions;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {ol.style.Point}
|
||||
* @param {ol.style.IconOptions} options Symbolizer properties.
|
||||
*/
|
||||
ol.style.Icon = function(options) {
|
||||
|
||||
goog.asserts.assert(options.url, 'url must be set');
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.url_ = (options.url instanceof ol.Expression) ?
|
||||
options.url : new ol.ExpressionLiteral(options.url);
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.width_ = !goog.isDef(options.width) ?
|
||||
null :
|
||||
(options.width instanceof ol.Expression) ?
|
||||
options.width : new ol.ExpressionLiteral(options.width);
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.height_ = !goog.isDef(options.height) ?
|
||||
null :
|
||||
(options.height instanceof ol.Expression) ?
|
||||
options.height : new ol.ExpressionLiteral(options.height);
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.opacity_ = !goog.isDef(options.opacity) ?
|
||||
new ol.ExpressionLiteral(ol.style.IconDefaults.opacity) :
|
||||
(options.opacity instanceof ol.Expression) ?
|
||||
options.opacity : new ol.ExpressionLiteral(options.opacity);
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.rotation_ = !goog.isDef(options.rotation) ?
|
||||
new ol.ExpressionLiteral(ol.style.IconDefaults.rotation) :
|
||||
(options.rotation instanceof ol.Expression) ?
|
||||
options.rotation : new ol.ExpressionLiteral(options.rotation);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return {ol.style.IconLiteral} Literal shape symbolizer.
|
||||
*/
|
||||
ol.style.Icon.prototype.createLiteral = function(feature) {
|
||||
var attrs = feature.getAttributes();
|
||||
|
||||
var url = /** @type {string} */ (this.url_.evaluate(feature, attrs));
|
||||
goog.asserts.assert(goog.isString(url) && url != '#', 'url must be a string');
|
||||
|
||||
var width = /** @type {number|undefined} */ (goog.isNull(this.width_) ?
|
||||
undefined : this.width_.evaluate(feature, attrs));
|
||||
goog.asserts.assert(!goog.isDef(width) || goog.isNumber(width),
|
||||
'width must be undefined or a number');
|
||||
|
||||
var height = /** @type {number|undefined} */ (goog.isNull(this.height_) ?
|
||||
undefined : this.height_.evaluate(feature, attrs));
|
||||
goog.asserts.assert(!goog.isDef(height) || goog.isNumber(height),
|
||||
'height must be undefined or a number');
|
||||
|
||||
var opacity = /** {@type {number} */ (this.opacity_.evaluate(feature, attrs));
|
||||
goog.asserts.assertNumber(opacity, 'opacity must be a number');
|
||||
|
||||
var rotation =
|
||||
/** {@type {number} */ (this.opacity_.evaluate(feature, attrs));
|
||||
goog.asserts.assertNumber(rotation, 'rotation must be a number');
|
||||
|
||||
return new ol.style.IconLiteral({
|
||||
url: url,
|
||||
width: width,
|
||||
height: height,
|
||||
opacity: opacity,
|
||||
rotation: rotation
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @type {ol.style.IconLiteral}
|
||||
*/
|
||||
ol.style.IconDefaults = new ol.style.IconLiteral({
|
||||
url: '#',
|
||||
opacity: 1,
|
||||
rotation: 0
|
||||
});
|
||||
@@ -185,7 +185,7 @@ describe('ol.parser.GeoJSON', function() {
|
||||
|
||||
var callback = function(feature, type) {
|
||||
return lookup[type];
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.readFeaturesFromString(text, {callback: callback});
|
||||
expect(result.length).toBe(179);
|
||||
|
||||
Reference in New Issue
Block a user