Basic support for text symbolizers

This adds basic label rendering for points and polygons to the
canvas renderer, and a text symbolizer to the style package.
This commit is contained in:
ahocevar
2013-06-10 10:08:08 +02:00
parent 79980bc7be
commit c17424deec
9 changed files with 378 additions and 31 deletions

View File

@@ -4,13 +4,17 @@ goog.require('ol.RendererHint');
goog.require('ol.View2D'); goog.require('ol.View2D');
goog.require('ol.control.defaults'); goog.require('ol.control.defaults');
goog.require('ol.filter.Filter'); goog.require('ol.filter.Filter');
goog.require('ol.filter.Geometry');
goog.require('ol.geom.GeometryType');
goog.require('ol.layer.Vector'); goog.require('ol.layer.Vector');
goog.require('ol.parser.GeoJSON'); goog.require('ol.parser.GeoJSON');
goog.require('ol.proj'); goog.require('ol.proj');
goog.require('ol.source.Vector'); goog.require('ol.source.Vector');
goog.require('ol.style.Line'); goog.require('ol.style.Line');
goog.require('ol.style.Rule'); goog.require('ol.style.Rule');
goog.require('ol.style.Shape');
goog.require('ol.style.Style'); goog.require('ol.style.Style');
goog.require('ol.style.Text');
var style = new ol.style.Style({rules: [ var style = new ol.style.Style({rules: [
@@ -42,6 +46,21 @@ var style = new ol.style.Style({rules: [
opacity: 1 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', 'type': 'LineString',
'coordinates': [[10000000, -10000000], [-10000000, -10000000]] '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')); }, new ol.parser.GeoJSON(), ol.proj.get('EPSG:3857'));

View File

@@ -37,7 +37,7 @@
<div class="span4"> <div class="span4">
<h4 id="title">Vector layer example</h4> <h4 id="title">Vector layer example</h4>
<p id="shortdesc">Example of a countries vector layer with country information on hover.</p> <p id="shortdesc">Example of a countries vector layer with country information on hover and country labels at higher zoom levels.</p>
<div id="docs"> <div id="docs">
<p>See the <a href="vector-layer.js" target="_blank">vector-layer.js source</a> to see how this is done.</p> <p>See the <a href="vector-layer.js" target="_blank">vector-layer.js source</a> to see how this is done.</p>
</div> </div>

View File

@@ -1,6 +1,8 @@
goog.require('ol.Expression');
goog.require('ol.Map'); goog.require('ol.Map');
goog.require('ol.RendererHint'); goog.require('ol.RendererHint');
goog.require('ol.View2D'); goog.require('ol.View2D');
goog.require('ol.filter.Filter');
goog.require('ol.layer.TileLayer'); goog.require('ol.layer.TileLayer');
goog.require('ol.layer.Vector'); goog.require('ol.layer.Vector');
goog.require('ol.parser.GeoJSON'); goog.require('ol.parser.GeoJSON');
@@ -10,6 +12,7 @@ goog.require('ol.source.Vector');
goog.require('ol.style.Polygon'); goog.require('ol.style.Polygon');
goog.require('ol.style.Rule'); goog.require('ol.style.Rule');
goog.require('ol.style.Style'); goog.require('ol.style.Style');
goog.require('ol.style.Text');
var raster = new ol.layer.TileLayer({ var raster = new ol.layer.TileLayer({
@@ -27,6 +30,19 @@ var vector = new ol.layer.Vector({
strokeColor: '#bada55' 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) { transformFeatureInfo: function(features) {

View File

@@ -573,6 +573,15 @@
* @property {Array.<ol.style.Rule>} rules Rules. * @property {Array.<ol.style.Rule>} 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 * @typedef {Object} ol.tilegrid.TileGridOptions
* @property {number|undefined} minZoom Minimum zoom. * @property {number|undefined} minZoom Minimum zoom.

View File

@@ -11,6 +11,7 @@ goog.require('ol.proj');
goog.require('ol.source.Vector'); goog.require('ol.source.Vector');
goog.require('ol.structs.RTree'); goog.require('ol.structs.RTree');
goog.require('ol.style.Style'); goog.require('ol.style.Style');
goog.require('ol.style.TextLiteral');
@@ -276,14 +277,17 @@ ol.layer.Vector.prototype.getPolygonVertices = function() {
/** /**
* @param {Object.<string, ol.Feature>} features Features. * @param {Object.<string, ol.Feature>} features Features.
* @return {Array.<Array>} symbolizers for features. * @return {Array.<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 = ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral =
function(features) { function(features) {
var uniqueLiterals = {}, var uniqueLiterals = {},
featuresBySymbolizer = [], featuresBySymbolizer = [],
style = this.style_, 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) { for (i in features) {
feature = features[i]; feature = features[i];
literals = feature.getSymbolizerLiterals(); literals = feature.getSymbolizerLiterals();
@@ -305,9 +309,17 @@ ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral =
key = goog.getUid(literal); key = goog.getUid(literal);
if (!goog.object.containsKey(uniqueLiterals, key)) { if (!goog.object.containsKey(uniqueLiterals, key)) {
uniqueLiterals[key] = featuresBySymbolizer.length; uniqueLiterals[key] = featuresBySymbolizer.length;
featuresBySymbolizer.push([[], literal]); featuresBySymbolizer.push([
/** @type {Array.<ol.Feature>} */ ([]),
/** @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; return featuresBySymbolizer;

View File

@@ -446,7 +446,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
group = groups[j]; group = groups[j];
deferred = sketchCanvasRenderer.renderFeaturesByGeometryType( deferred = sketchCanvasRenderer.renderFeaturesByGeometryType(
/** @type {ol.geom.GeometryType} */ (type), /** @type {ol.geom.GeometryType} */ (type),
group[0], group[1]); group[0], group[1], group[2]);
if (deferred) { if (deferred) {
break renderByGeometryType; break renderByGeometryType;
} }

View File

@@ -8,6 +8,8 @@ goog.require('goog.events');
goog.require('goog.events.EventType'); goog.require('goog.events.EventType');
goog.require('goog.vec.Mat4'); goog.require('goog.vec.Mat4');
goog.require('ol.Feature'); goog.require('ol.Feature');
goog.require('ol.extent');
goog.require('ol.geom.AbstractCollection');
goog.require('ol.geom.Geometry'); goog.require('ol.geom.Geometry');
goog.require('ol.geom.GeometryType'); goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString'); goog.require('ol.geom.LineString');
@@ -23,6 +25,7 @@ goog.require('ol.style.PolygonLiteral');
goog.require('ol.style.ShapeLiteral'); goog.require('ol.style.ShapeLiteral');
goog.require('ol.style.ShapeType'); goog.require('ol.style.ShapeType');
goog.require('ol.style.SymbolizerLiteral'); 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 {ol.geom.GeometryType} type Geometry type.
* @param {Array.<ol.Feature>} features Array of features. * @param {Array.<ol.Feature>} features Array of features.
* @param {ol.style.SymbolizerLiteral} symbolizer Symbolizer. * @param {ol.style.SymbolizerLiteral} symbolizer Symbolizer.
* @param {Array} data Additional data.
* @return {boolean} true if deferred, false if rendered. * @return {boolean} true if deferred, false if rendered.
*/ */
ol.renderer.canvas.VectorRenderer.prototype.renderFeaturesByGeometryType = ol.renderer.canvas.VectorRenderer.prototype.renderFeaturesByGeometryType =
function(type, features, symbolizer) { function(type, features, symbolizer, data) {
var deferred = false; var deferred = false;
switch (type) { if (!(symbolizer instanceof ol.style.TextLiteral)) {
case ol.geom.GeometryType.POINT: switch (type) {
case ol.geom.GeometryType.MULTIPOINT: case ol.geom.GeometryType.POINT:
goog.asserts.assert(symbolizer instanceof ol.style.PointLiteral, case ol.geom.GeometryType.MULTIPOINT:
'Expected point symbolizer: ' + symbolizer); goog.asserts.assert(symbolizer instanceof ol.style.PointLiteral,
deferred = this.renderPointFeatures_( 'Expected point symbolizer: ' + symbolizer);
features, /** @type {ol.style.PointLiteral} */ (symbolizer)); deferred = this.renderPointFeatures_(
break; features, /** @type {ol.style.PointLiteral} */ (symbolizer));
case ol.geom.GeometryType.LINESTRING: break;
case ol.geom.GeometryType.MULTILINESTRING: case ol.geom.GeometryType.LINESTRING:
goog.asserts.assert(symbolizer instanceof ol.style.LineLiteral, case ol.geom.GeometryType.MULTILINESTRING:
'Expected line symbolizer: ' + symbolizer); goog.asserts.assert(symbolizer instanceof ol.style.LineLiteral,
this.renderLineStringFeatures_( 'Expected line symbolizer: ' + symbolizer);
features, /** @type {ol.style.LineLiteral} */ (symbolizer)); this.renderLineStringFeatures_(
break; features, /** @type {ol.style.LineLiteral} */ (symbolizer));
case ol.geom.GeometryType.POLYGON: break;
case ol.geom.GeometryType.MULTIPOLYGON: case ol.geom.GeometryType.POLYGON:
goog.asserts.assert(symbolizer instanceof ol.style.PolygonLiteral, case ol.geom.GeometryType.MULTIPOLYGON:
'Expected polygon symbolizer: ' + symbolizer); goog.asserts.assert(symbolizer instanceof ol.style.PolygonLiteral,
this.renderPolygonFeatures_( 'Expected polygon symbolizer: ' + symbolizer);
features, /** @type {ol.style.PolygonLiteral} */ (symbolizer)); this.renderPolygonFeatures_(
break; features, /** @type {ol.style.PolygonLiteral} */ (symbolizer));
default: break;
throw new Error('Rendering not implemented for geometry type: ' + type); default:
throw new Error('Rendering not implemented for geometry type: ' + type);
}
} else {
this.renderLabels_(features, symbolizer, data);
} }
return deferred; return deferred;
}; };
@@ -255,6 +263,52 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ =
}; };
/**
* @param {Array.<ol.Feature>} 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.<ol.Feature>} features Array of polygon features. * @param {Array.<ol.Feature>} features Array of polygon features.
* @param {ol.style.PolygonLiteral} symbolizer Polygon symbolizer. * @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.<goog.vec.Vec3.AnyType>} 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. * @param {ol.style.ShapeLiteral} shape Shape symbolizer.
* @return {!HTMLCanvasElement} Canvas element. * @return {!HTMLCanvasElement} Canvas element.

View File

@@ -4,6 +4,7 @@
@exportClass ol.style.Rule ol.style.RuleOptions @exportClass ol.style.Rule ol.style.RuleOptions
@exportClass ol.style.Shape ol.style.ShapeOptions @exportClass ol.style.Shape ol.style.ShapeOptions
@exportClass ol.style.Style ol.style.StyleOptions @exportClass ol.style.Style ol.style.StyleOptions
@exportClass ol.style.Text ol.style.TextOptions
@exportSymbol ol.style.IconType @exportSymbol ol.style.IconType
@exportSymbol ol.style.ShapeType @exportSymbol ol.style.ShapeType
@exportProperty ol.style.ShapeType.CIRCLE @exportProperty ol.style.ShapeType.CIRCLE

171
src/ol/style/text.js Normal file
View File

@@ -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
});