diff --git a/examples/style-rules.js b/examples/style-rules.js
index a7d3a708d2..b41f720a5a 100644
--- a/examples/style-rules.js
+++ b/examples/style-rules.js
@@ -4,13 +4,17 @@ goog.require('ol.RendererHint');
goog.require('ol.View2D');
goog.require('ol.control.defaults');
goog.require('ol.filter.Filter');
+goog.require('ol.filter.Geometry');
+goog.require('ol.geom.GeometryType');
goog.require('ol.layer.Vector');
goog.require('ol.parser.GeoJSON');
goog.require('ol.proj');
goog.require('ol.source.Vector');
goog.require('ol.style.Line');
goog.require('ol.style.Rule');
+goog.require('ol.style.Shape');
goog.require('ol.style.Style');
+goog.require('ol.style.Text');
var style = new ol.style.Style({rules: [
@@ -42,6 +46,21 @@ var style = new ol.style.Style({rules: [
opacity: 1
})
]
+ }),
+ new ol.style.Rule({
+ filter: new ol.filter.Geometry(ol.geom.GeometryType.POINT),
+ symbolizers: [
+ new ol.style.Shape({
+ size: 40,
+ fillColor: '#013'
+ }),
+ new ol.style.Text({
+ color: '#bada55',
+ name: new ol.Expression('label'),
+ fontFamily: 'Calibri,sans-serif',
+ fontSize: 14
+ })
+ ]
})
]});
@@ -114,6 +133,42 @@ vector.parseFeatures({
'type': 'LineString',
'coordinates': [[10000000, -10000000], [-10000000, -10000000]]
}
+ }, {
+ 'type': 'Feature',
+ 'properties': {
+ 'label': 'South'
+ },
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [0, -6000000]
+ }
+ }, {
+ 'type': 'Feature',
+ 'properties': {
+ 'label': 'West'
+ },
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [-6000000, 0]
+ }
+ }, {
+ 'type': 'Feature',
+ 'properties': {
+ 'label': 'North'
+ },
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [0, 6000000]
+ }
+ }, {
+ 'type': 'Feature',
+ 'properties': {
+ 'label': 'East'
+ },
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [6000000, 0]
+ }
}]
}, new ol.parser.GeoJSON(), ol.proj.get('EPSG:3857'));
diff --git a/examples/vector-layer.html b/examples/vector-layer.html
index 6cea238384..2b8a08a591 100644
--- a/examples/vector-layer.html
+++ b/examples/vector-layer.html
@@ -37,7 +37,7 @@
Vector layer example
-
Example of a countries vector layer with country information on hover.
+
Example of a countries vector layer with country information on hover and country labels at higher zoom levels.
diff --git a/examples/vector-layer.js b/examples/vector-layer.js
index 2a55cbba93..a4a00eeeaa 100644
--- a/examples/vector-layer.js
+++ b/examples/vector-layer.js
@@ -1,6 +1,8 @@
+goog.require('ol.Expression');
goog.require('ol.Map');
goog.require('ol.RendererHint');
goog.require('ol.View2D');
+goog.require('ol.filter.Filter');
goog.require('ol.layer.TileLayer');
goog.require('ol.layer.Vector');
goog.require('ol.parser.GeoJSON');
@@ -10,6 +12,7 @@ goog.require('ol.source.Vector');
goog.require('ol.style.Polygon');
goog.require('ol.style.Rule');
goog.require('ol.style.Style');
+goog.require('ol.style.Text');
var raster = new ol.layer.TileLayer({
@@ -27,6 +30,19 @@ var vector = new ol.layer.Vector({
strokeColor: '#bada55'
})
]
+ }),
+ new ol.style.Rule({
+ filter: new ol.filter.Filter(function() {
+ return map.getView().getResolution() < 5000;
+ }),
+ symbolizers: [
+ new ol.style.Text({
+ color: '#bada55',
+ name: new ol.Expression('name'),
+ fontFamily: 'Calibri,sans-serif',
+ fontSize: 12
+ })
+ ]
})
]}),
transformFeatureInfo: function(features) {
diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc
index bc61df1fcb..f149bca03e 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -573,6 +573,15 @@
* @property {Array.
} rules Rules.
*/
+/**
+ * @typedef {Object} ol.style.TextOptions
+ * @property {string|ol.Expression|undefined} color Color.
+ * @property {string|ol.Expression|undefined} fontFamily Font family.
+ * @property {number|ol.Expression|undefined} fontSize Font size in pixels.
+ * @property {string|ol.Expression} name Name (i.e. text content) of the label.
+ * @property {number|ol.Expression|undefined} opacity Opacity (0-1).
+ */
+
/**
* @typedef {Object} ol.tilegrid.TileGridOptions
* @property {number|undefined} minZoom Minimum zoom.
diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js
index 21b2f865b4..42f56eca70 100644
--- a/src/ol/layer/vectorlayer.js
+++ b/src/ol/layer/vectorlayer.js
@@ -11,6 +11,7 @@ goog.require('ol.proj');
goog.require('ol.source.Vector');
goog.require('ol.structs.RTree');
goog.require('ol.style.Style');
+goog.require('ol.style.TextLiteral');
@@ -276,14 +277,17 @@ ol.layer.Vector.prototype.getPolygonVertices = function() {
/**
* @param {Object.} features Features.
- * @return {Array.} symbolizers for features.
+ * @return {Array.} symbolizers for features. Each array in this array
+ * contains 3 items: an array of features, the symbolizer literal, and
+ * an array with optional additional data for each feature.
*/
ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral =
function(features) {
var uniqueLiterals = {},
featuresBySymbolizer = [],
style = this.style_,
- i, j, l, feature, literals, numLiterals, literal, uniqueLiteral, key;
+ i, j, l, feature, literals, numLiterals, literal, uniqueLiteral, key,
+ item;
for (i in features) {
feature = features[i];
literals = feature.getSymbolizerLiterals();
@@ -305,9 +309,17 @@ ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral =
key = goog.getUid(literal);
if (!goog.object.containsKey(uniqueLiterals, key)) {
uniqueLiterals[key] = featuresBySymbolizer.length;
- featuresBySymbolizer.push([[], literal]);
+ featuresBySymbolizer.push([
+ /** @type {Array.} */ ([]),
+ /** @type {ol.style.SymbolizerLiteral} */ (literal),
+ /** @type {Array} */ ([])
+ ]);
+ }
+ item = featuresBySymbolizer[uniqueLiterals[key]];
+ item[0].push(feature);
+ if (literal instanceof ol.style.TextLiteral) {
+ item[2].push(literals[j].name);
}
- featuresBySymbolizer[uniqueLiterals[key]][0].push(feature);
}
}
return featuresBySymbolizer;
diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
index 22a399fd4a..5d7b0d1081 100644
--- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
+++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
@@ -446,7 +446,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
group = groups[j];
deferred = sketchCanvasRenderer.renderFeaturesByGeometryType(
/** @type {ol.geom.GeometryType} */ (type),
- group[0], group[1]);
+ group[0], group[1], group[2]);
if (deferred) {
break renderByGeometryType;
}
diff --git a/src/ol/renderer/canvas/canvasvectorrenderer.js b/src/ol/renderer/canvas/canvasvectorrenderer.js
index cb46ae288d..a8f86d33e1 100644
--- a/src/ol/renderer/canvas/canvasvectorrenderer.js
+++ b/src/ol/renderer/canvas/canvasvectorrenderer.js
@@ -8,6 +8,8 @@ goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.vec.Mat4');
goog.require('ol.Feature');
+goog.require('ol.extent');
+goog.require('ol.geom.AbstractCollection');
goog.require('ol.geom.Geometry');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
@@ -23,6 +25,7 @@ goog.require('ol.style.PolygonLiteral');
goog.require('ol.style.ShapeLiteral');
goog.require('ol.style.ShapeType');
goog.require('ol.style.SymbolizerLiteral');
+goog.require('ol.style.TextLiteral');
@@ -100,35 +103,40 @@ ol.renderer.canvas.VectorRenderer.prototype.getMaxSymbolSize = function() {
* @param {ol.geom.GeometryType} type Geometry type.
* @param {Array.} features Array of features.
* @param {ol.style.SymbolizerLiteral} symbolizer Symbolizer.
+ * @param {Array} data Additional data.
* @return {boolean} true if deferred, false if rendered.
*/
ol.renderer.canvas.VectorRenderer.prototype.renderFeaturesByGeometryType =
- function(type, features, symbolizer) {
+ function(type, features, symbolizer, data) {
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);
+ if (!(symbolizer instanceof ol.style.TextLiteral)) {
+ 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);
+ }
+ } else {
+ this.renderLabels_(features, symbolizer, data);
}
return deferred;
};
@@ -255,6 +263,52 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ =
};
+/**
+ * @param {Array.} features Array of features.
+ * @param {ol.style.TextLiteral} text Text symbolizer.
+ * @param {Array} names Label text for each feature.
+ * @private
+ */
+ol.renderer.canvas.VectorRenderer.prototype.renderLabels_ =
+ function(features, text, names) {
+ var context = this.context_,
+ fontArray = [],
+ color = text.color,
+ vecs, vec;
+
+ context.save();
+ context.scale(1, -1);
+
+ if (color) {
+ context.fillStyle = color;
+ }
+ if (goog.isDef(text.fontSize)) {
+ fontArray.push((text.fontSize * this.inverseScale_) + 'px');
+ }
+ if (goog.isDef(text.fontFamily)) {
+ fontArray.push(text.fontFamily);
+ }
+ if (fontArray.length) {
+ context.font = fontArray.join(' ');
+ }
+ context.globalAlpha = text.opacity;
+ context.textAlign = 'center';
+ context.textBaseline = 'middle';
+
+ for (var i = 0, ii = features.length; i < ii; ++i) {
+ vecs = ol.renderer.canvas.VectorRenderer.getLabelVectors(
+ features[i].getGeometry());
+ for (var j = 0, jj = vecs.length; j < jj; ++j) {
+ vec = vecs[j];
+ goog.vec.Mat4.multVec3(this.transform_, vec, vec);
+ context.fillText(names[i], vec[0], vec[1]);
+ }
+ }
+
+ context.restore();
+};
+
+
/**
* @param {Array.} features Array of polygon features.
* @param {ol.style.PolygonLiteral} symbolizer Polygon symbolizer.
@@ -384,6 +438,35 @@ ol.renderer.canvas.VectorRenderer.renderCircle_ = function(circle) {
};
+/**
+ * @param {ol.geom.Geometry} geometry Geometry.
+ * @return {Array.} Renderable geometry vectors.
+ */
+ol.renderer.canvas.VectorRenderer.getLabelVectors = function(geometry) {
+ if (geometry instanceof ol.geom.AbstractCollection) {
+ var components = geometry.components;
+ var numComponents = components.length;
+ var result = [];
+ for (var i = 0; i < numComponents; ++i) {
+ result.push.apply(result,
+ ol.renderer.canvas.VectorRenderer.getLabelVectors(components[i]));
+ }
+ return result;
+ }
+ var type = geometry.getType();
+ if (type == ol.geom.GeometryType.POINT) {
+ return [[geometry.get(0), geometry.get(1), 0]];
+ }
+ if (type == ol.geom.GeometryType.POLYGON) {
+ // TODO Better label placement
+ var coordinates = ol.extent.getCenter(geometry.getBounds());
+ return [[coordinates[0], coordinates[1], 0]];
+ }
+ throw new Error('Label rendering not implemented for geometry type: ' +
+ type);
+};
+
+
/**
* @param {ol.style.ShapeLiteral} shape Shape symbolizer.
* @return {!HTMLCanvasElement} Canvas element.
diff --git a/src/ol/style.exports b/src/ol/style.exports
index 8df1a0a1bd..d2e83eb60b 100644
--- a/src/ol/style.exports
+++ b/src/ol/style.exports
@@ -4,6 +4,7 @@
@exportClass ol.style.Rule ol.style.RuleOptions
@exportClass ol.style.Shape ol.style.ShapeOptions
@exportClass ol.style.Style ol.style.StyleOptions
+@exportClass ol.style.Text ol.style.TextOptions
@exportSymbol ol.style.IconType
@exportSymbol ol.style.ShapeType
@exportProperty ol.style.ShapeType.CIRCLE
diff --git a/src/ol/style/text.js b/src/ol/style/text.js
new file mode 100644
index 0000000000..4a260f395a
--- /dev/null
+++ b/src/ol/style/text.js
@@ -0,0 +1,171 @@
+goog.provide('ol.style.Text');
+goog.provide('ol.style.TextLiteral');
+
+goog.require('goog.asserts');
+goog.require('ol.Expression');
+goog.require('ol.ExpressionLiteral');
+goog.require('ol.style.Symbolizer');
+goog.require('ol.style.SymbolizerLiteral');
+
+
+/**
+ * @typedef {{color: (string|undefined),
+ * fontFamily: (string|undefined),
+ * fontSize: number,
+ * name: string,
+ * opacity: number}}
+ */
+ol.style.TextLiteralOptions;
+
+
+
+/**
+ * @constructor
+ * @extends {ol.style.SymbolizerLiteral}
+ * @param {ol.style.TextLiteralOptions} options Text literal options.
+ */
+ol.style.TextLiteral = function(options) {
+
+ /** @type {string|undefined} */
+ this.color = options.color;
+ if (goog.isDef(options.color)) {
+ goog.asserts.assertString(options.color, 'color must be a string');
+ }
+
+ /** @type {string|undefined} */
+ this.fontFamily = options.fontFamily;
+ if (goog.isDef(options.fontFamily)) {
+ goog.asserts.assertString(options.fontFamily,
+ 'fontFamily must be a string');
+ }
+
+ goog.asserts.assertNumber(options.fontSize, 'fontSize must be a number');
+ /** @type {number} */
+ this.fontSize = options.fontSize;
+
+ goog.asserts.assertString(options.name, 'name must be a string');
+ /** @type {string} */
+ this.name = options.name;
+
+ goog.asserts.assertNumber(options.opacity, 'opacity must be a number');
+ /** @type {number} */
+ this.opacity = options.opacity;
+
+};
+goog.inherits(ol.style.TextLiteral, ol.style.SymbolizerLiteral);
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.TextLiteral.prototype.equals = function(textLiteral) {
+ return this.color == textLiteral.color &&
+ this.fontFamily == textLiteral.fontFamily &&
+ this.fontSize == textLiteral.fontSize &&
+ this.opacity == textLiteral.opacity;
+};
+
+
+
+/**
+ * @constructor
+ * @extends {ol.style.Symbolizer}
+ * @param {ol.style.TextOptions} options Text options.
+ */
+ol.style.Text = function(options) {
+
+ /**
+ * @type {ol.Expression}
+ * @private
+ */
+ this.color_ = !goog.isDef(options.color) ?
+ null :
+ (options.color instanceof ol.Expression) ?
+ options.color : new ol.ExpressionLiteral(options.color);
+
+ /**
+ * @type {ol.Expression}
+ * @private
+ */
+ this.fontFamily_ = !goog.isDefAndNotNull(options.fontFamily) ?
+ null :
+ (options.fontFamily instanceof ol.Expression) ?
+ options.fontFamily : new ol.ExpressionLiteral(options.fontFamily);
+
+ /**
+ * @type {ol.Expression}
+ * @private
+ */
+ this.fontSize_ = !goog.isDefAndNotNull(options.fontSize) ?
+ null :
+ (options.fontSize instanceof ol.Expression) ?
+ options.fontSize : new ol.ExpressionLiteral(options.fontSize);
+
+ /**
+ * @type {ol.Expression}
+ * @private
+ */
+ this.name_ = (options.name instanceof ol.Expression) ?
+ options.name : new ol.ExpressionLiteral(options.name);
+
+ /**
+ * @type {ol.Expression}
+ * @private
+ */
+ this.opacity_ = !goog.isDef(options.opacity) ?
+ new ol.ExpressionLiteral(ol.style.TextDefaults.opacity) :
+ (options.opacity instanceof ol.Expression) ?
+ options.opacity : new ol.ExpressionLiteral(options.opacity);
+
+};
+goog.inherits(ol.style.Text, ol.style.Symbolizer);
+
+
+/**
+ * @inheritDoc
+ * @return {ol.style.TextLiteral} Literal text symbolizer.
+ */
+ol.style.Text.prototype.createLiteral = function(opt_feature) {
+ var attrs,
+ feature = opt_feature;
+ if (goog.isDef(feature)) {
+ attrs = feature.getAttributes();
+ }
+
+ var color = goog.isNull(this.color_) ?
+ undefined :
+ /** @type {string} */ (this.color_.evaluate(feature, attrs));
+ goog.asserts.assert(!goog.isDef(color) || goog.isString(color));
+
+ var fontFamily = goog.isNull(this.fontFamily_) ?
+ undefined :
+ /** @type {string} */ (this.fontFamily_.evaluate(feature, attrs));
+ goog.asserts.assert(!goog.isDef(fontFamily) || goog.isString(fontFamily));
+
+ var fontSize = this.fontSize_.evaluate(feature, attrs);
+ goog.asserts.assertNumber(fontSize, 'fontSize must be a number');
+
+ var name = this.name_.evaluate(feature, attrs);
+ goog.asserts.assertString(name, 'name must be a string');
+
+ var opacity = this.opacity_.evaluate(feature, attrs);
+ goog.asserts.assertNumber(opacity, 'opacity must be a number');
+
+ return new ol.style.TextLiteral({
+ color: color,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ name: name,
+ opacity: opacity
+ });
+};
+
+
+/**
+ * @type {ol.style.TextLiteral}
+ */
+ol.style.TextDefaults = new ol.style.TextLiteral({
+ fontSize: 10,
+ name: '',
+ opacity: 1
+});