Merge pull request #790 from tschaub/expression

Expression parsing
This commit is contained in:
Tim Schaub
2013-06-26 16:40:08 -07:00
40 changed files with 4832 additions and 1011 deletions

3
src/ol/expr.jsdoc Normal file
View File

@@ -0,0 +1,3 @@
/**
* @namespace ol.expr
*/

View File

@@ -0,0 +1,2 @@
@exportSymbol ol.expr.parse
@exportSymbol ol.expr.register

158
src/ol/expr/expression.js Normal file
View File

@@ -0,0 +1,158 @@
goog.provide('ol.expr');
goog.provide('ol.expr.functions');
goog.require('ol.Extent');
goog.require('ol.Feature');
goog.require('ol.expr.Call');
goog.require('ol.expr.Expression');
goog.require('ol.expr.Identifier');
goog.require('ol.expr.Parser');
goog.require('ol.extent');
goog.require('ol.geom.GeometryType');
/**
* Evaluate an expression with a feature. The feature attributes will be used
* as the evaluation scope. The `ol.expr.lib` functions will be used as
* function scope. The feature itself will be used as the `this` argument.
*
* @param {ol.expr.Expression} expr The expression.
* @param {ol.Feature=} opt_feature The feature.
* @return {*} The result of the expression.
*/
ol.expr.evaluateFeature = function(expr, opt_feature) {
var result;
if (goog.isDef(opt_feature)) {
result = expr.evaluate(
opt_feature.getAttributes(), ol.expr.lib, opt_feature);
} else {
result = expr.evaluate();
}
return result;
};
/**
* Parse an expression.
* @param {string} source The expression source (e.g. `'foo + 2'`).
* @return {ol.expr.Expression} An expression instance that can be
* evaluated within some scope to provide a value.
*/
ol.expr.parse = function(source) {
var parser = new ol.expr.Parser();
return parser.parse(source);
};
/**
* Register a library function to be used in expressions.
* @param {string} name The function name (e.g. 'myFunc').
* @param {function(this:ol.Feature)} func The function to be called in an
* expression. This function will be called with a feature as the `this`
* argument when the expression is evaluated in the context of a features.
*/
ol.expr.register = function(name, func) {
ol.expr.lib[name] = func;
};
/**
* Determines whether an expression is a call expression that calls one of the
* `ol.expr.lib` functions.
*
* @param {ol.expr.Expression} expr The candidate expression.
* @return {string|undefined} If the candidate expression is a call to a lib
* function, the return will be the function name. If not, the return will be
* `undefined`.
*/
ol.expr.isLibCall = function(expr) {
var name;
if (expr instanceof ol.expr.Call) {
var callee = expr.getCallee();
if (callee instanceof ol.expr.Identifier) {
name = callee.getName();
if (!ol.expr.lib.hasOwnProperty(name)) {
name = undefined;
}
}
}
return name;
};
/**
* Library of well-known functions. These are available to expressions parsed
* with `ol.expr.parse`.
*
* @type {Object.<string, function(...)>}
*/
ol.expr.lib = {};
/**
* Enumeration of library function names.
*
* @enum {string}
*/
ol.expr.functions = {
EXTENT: 'extent',
FID: 'fid',
GEOMETRY_TYPE: 'geometryType'
};
/**
* Determine if a feature's extent intersects the provided extent.
* @param {number} minX Minimum x-coordinate value.
* @param {number} maxX Maximum x-coordinate value.
* @param {number} minY Minimum y-coordinate value.
* @param {number} maxY Maximum y-coordinate value.
* @return {boolean} The provided extent intersects the feature's extent.
* @this {ol.Feature}
*/
ol.expr.lib[ol.expr.functions.EXTENT] = function(minX, maxX, minY, maxY) {
var intersects = false;
var geometry = this.getGeometry();
if (geometry) {
intersects = ol.extent.intersects(geometry.getBounds(),
[minX, maxX, minY, maxY]);
}
return intersects;
};
/**
* Determine if the feature identifier matches any of the provided values.
* @param {...string} var_args Feature identifiers.
* @return {boolean} The feature's identifier matches one of the given values.
* @this {ol.Feature}
*/
ol.expr.lib[ol.expr.functions.FID] = function(var_args) {
var matches = false;
var id = this.getFeatureId();
if (goog.isDef(id)) {
for (var i = 0, ii = arguments.length; i < ii; ++i) {
if (arguments[i] === id) {
matches = true;
break;
}
}
}
return matches;
};
/**
* Determine if a feature's default geometry is of the given type.
* @param {ol.geom.GeometryType} type Geometry type.
* @return {boolean} The feature's default geometry is of the given type.
* @this {ol.Feature}
*/
ol.expr.lib[ol.expr.functions.GEOMETRY_TYPE] = function(type) {
var same = false;
var geometry = this.getGeometry();
if (geometry) {
same = geometry.getType() === type;
}
return same;
};

631
src/ol/expr/expressions.js Normal file
View File

@@ -0,0 +1,631 @@
goog.provide('ol.expr.Call');
goog.provide('ol.expr.Comparison');
goog.provide('ol.expr.ComparisonOp');
goog.provide('ol.expr.Expression');
goog.provide('ol.expr.Identifier');
goog.provide('ol.expr.Literal');
goog.provide('ol.expr.Logical');
goog.provide('ol.expr.LogicalOp');
goog.provide('ol.expr.Math');
goog.provide('ol.expr.MathOp');
goog.provide('ol.expr.Member');
goog.provide('ol.expr.Not');
/**
* Base class for all expressions. Instances of ol.expr.Expression
* correspond to a limited set of ECMAScript 5.1 expressions.
* http://www.ecma-international.org/ecma-262/5.1/#sec-11
*
* This base class should not be constructed directly. Instead, use one of
* the subclass constructors.
*
* @constructor
*/
ol.expr.Expression = function() {};
/**
* Evaluate the expression and return the result.
*
* @param {Object=} opt_scope Evaluation scope. All properties of this object
* will be available as variables when evaluating the expression. If not
* provided, `null` will be used.
* @param {Object=} opt_fns Optional scope for looking up functions. If not
* provided, functions will be looked in the evaluation scope.
* @param {Object=} opt_this Object to use as this when evaluating call
* expressions. If not provided, `this` will resolve to a new object.
* @return {*} Result of the expression.
*/
ol.expr.Expression.prototype.evaluate = goog.abstractMethod;
/**
* A call expression (e.g. `foo(bar)`).
*
* @constructor
* @extends {ol.expr.Expression}
* @param {ol.expr.Expression} callee An expression that resolves to a
* function.
* @param {Array.<ol.expr.Expression>} args Arguments.
*/
ol.expr.Call = function(callee, args) {
/**
* @type {ol.expr.Expression}
* @private
*/
this.callee_ = callee;
/**
* @type {Array.<ol.expr.Expression>}
* @private
*/
this.args_ = args;
};
goog.inherits(ol.expr.Call, ol.expr.Expression);
/**
* @inheritDoc
*/
ol.expr.Call.prototype.evaluate = function(opt_scope, opt_fns, opt_this) {
var fnScope = goog.isDefAndNotNull(opt_fns) ? opt_fns : opt_scope;
var fn = this.callee_.evaluate(fnScope);
if (!fn || !goog.isFunction(fn)) {
throw new Error('Expected function but found ' + fn);
}
var thisArg = goog.isDef(opt_this) ? opt_this : {};
var len = this.args_.length;
var values = new Array(len);
for (var i = 0; i < len; ++i) {
values[i] = this.args_[i].evaluate(opt_scope, opt_fns, opt_this);
}
return fn.apply(thisArg, values);
};
/**
* Get the argument list.
* @return {Array.<ol.expr.Expression>} The argument.
*/
ol.expr.Call.prototype.getArgs = function() {
return this.args_;
};
/**
* Get the callee expression.
* @return {ol.expr.Expression} The callee expression.
*/
ol.expr.Call.prototype.getCallee = function() {
return this.callee_;
};
/**
* @enum {string}
*/
ol.expr.ComparisonOp = {
EQ: '==',
NEQ: '!=',
STRICT_EQ: '===',
STRICT_NEQ: '!==',
GT: '>',
LT: '<',
GTE: '>=',
LTE: '<='
};
/**
* A comparison expression (e.g. `foo >= 42`, `bar != "chicken"`).
*
* @constructor
* @extends {ol.expr.Expression}
* @param {ol.expr.ComparisonOp} operator Comparison operator.
* @param {ol.expr.Expression} left Left expression.
* @param {ol.expr.Expression} right Right expression.
*/
ol.expr.Comparison = function(operator, left, right) {
/**
* @type {ol.expr.ComparisonOp}
* @private
*/
this.operator_ = operator;
/**
* @type {ol.expr.Expression}
* @private
*/
this.left_ = left;
/**
* @type {ol.expr.Expression}
* @private
*/
this.right_ = right;
};
goog.inherits(ol.expr.Comparison, ol.expr.Expression);
/**
* Determine if a given string is a valid comparison operator.
* @param {string} candidate Operator to test.
* @return {boolean} The operator is valid.
*/
ol.expr.Comparison.isValidOp = (function() {
var valid = {};
for (var key in ol.expr.ComparisonOp) {
valid[ol.expr.ComparisonOp[key]] = true;
}
return function isValidOp(candidate) {
return !!valid[candidate];
};
}());
/**
* @inheritDoc
*/
ol.expr.Comparison.prototype.evaluate = function(opt_scope, opt_fns, opt_this) {
var result;
var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this);
var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this);
var op = this.operator_;
if (op === ol.expr.ComparisonOp.EQ) {
result = leftVal == rightVal;
} else if (op === ol.expr.ComparisonOp.NEQ) {
result = leftVal != rightVal;
} else if (op === ol.expr.ComparisonOp.STRICT_EQ) {
result = leftVal === rightVal;
} else if (op === ol.expr.ComparisonOp.STRICT_NEQ) {
result = leftVal !== rightVal;
} else if (op === ol.expr.ComparisonOp.GT) {
result = leftVal > rightVal;
} else if (op === ol.expr.ComparisonOp.LT) {
result = leftVal < rightVal;
} else if (op === ol.expr.ComparisonOp.GTE) {
result = leftVal >= rightVal;
} else if (op === ol.expr.ComparisonOp.LTE) {
result = leftVal <= rightVal;
} else {
throw new Error('Unsupported comparison operator: ' + this.operator_);
}
return result;
};
/**
* Get the comparison operator.
* @return {string} The comparison operator.
*/
ol.expr.Comparison.prototype.getOperator = function() {
return this.operator_;
};
/**
* Get the left expression.
* @return {ol.expr.Expression} The left expression.
*/
ol.expr.Comparison.prototype.getLeft = function() {
return this.left_;
};
/**
* Get the right expression.
* @return {ol.expr.Expression} The right expression.
*/
ol.expr.Comparison.prototype.getRight = function() {
return this.right_;
};
/**
* An identifier expression (e.g. `foo`).
*
* @constructor
* @extends {ol.expr.Expression}
* @param {string} name An identifier name.
*/
ol.expr.Identifier = function(name) {
/**
* @type {string}
* @private
*/
this.name_ = name;
};
goog.inherits(ol.expr.Identifier, ol.expr.Expression);
/**
* @inheritDoc
*/
ol.expr.Identifier.prototype.evaluate = function(opt_scope) {
if (!goog.isDefAndNotNull(opt_scope)) {
throw new Error('Attempt to evaluate identifier with no scope');
}
return opt_scope[this.name_];
};
/**
* Get the identifier name.
* @return {string} The identifier name.
*/
ol.expr.Identifier.prototype.getName = function() {
return this.name_;
};
/**
* A literal expression (e.g. `"chicken"`, `42`, `true`, `null`).
*
* @constructor
* @extends {ol.expr.Expression}
* @param {string|number|boolean|null} value A literal value.
*/
ol.expr.Literal = function(value) {
/**
* @type {string|number|boolean|null}
* @private
*/
this.value_ = value;
};
goog.inherits(ol.expr.Literal, ol.expr.Expression);
/**
* @inheritDoc
*/
ol.expr.Literal.prototype.evaluate = function() {
return this.value_;
};
/**
* Get the literal value.
* @return {string|number|boolean|null} The literal value.
*/
ol.expr.Literal.prototype.getValue = function() {
return this.value_;
};
/**
* @enum {string}
*/
ol.expr.LogicalOp = {
AND: '&&',
OR: '||'
};
/**
* A binary logical expression (e.g. `foo && bar`, `bar || "chicken"`).
*
* @constructor
* @extends {ol.expr.Expression}
* @param {ol.expr.LogicalOp} operator Logical operator.
* @param {ol.expr.Expression} left Left expression.
* @param {ol.expr.Expression} right Right expression.
*/
ol.expr.Logical = function(operator, left, right) {
/**
* @type {ol.expr.LogicalOp}
* @private
*/
this.operator_ = operator;
/**
* @type {ol.expr.Expression}
* @private
*/
this.left_ = left;
/**
* @type {ol.expr.Expression}
* @private
*/
this.right_ = right;
};
goog.inherits(ol.expr.Logical, ol.expr.Expression);
/**
* Determine if a given string is a valid logical operator.
* @param {string} candidate Operator to test.
* @return {boolean} The operator is valid.
*/
ol.expr.Logical.isValidOp = (function() {
var valid = {};
for (var key in ol.expr.LogicalOp) {
valid[ol.expr.LogicalOp[key]] = true;
}
return function isValidOp(candidate) {
return !!valid[candidate];
};
}());
/**
* @inheritDoc
*/
ol.expr.Logical.prototype.evaluate = function(opt_scope, opt_fns,
opt_this) {
var result;
var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this);
var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this);
if (this.operator_ === ol.expr.LogicalOp.AND) {
result = leftVal && rightVal;
} else if (this.operator_ === ol.expr.LogicalOp.OR) {
result = leftVal || rightVal;
} else {
throw new Error('Unsupported logical operator: ' + this.operator_);
}
return result;
};
/**
* Get the logical operator.
* @return {string} The logical operator.
*/
ol.expr.Logical.prototype.getOperator = function() {
return this.operator_;
};
/**
* Get the left expression.
* @return {ol.expr.Expression} The left expression.
*/
ol.expr.Logical.prototype.getLeft = function() {
return this.left_;
};
/**
* Get the right expression.
* @return {ol.expr.Expression} The right expression.
*/
ol.expr.Logical.prototype.getRight = function() {
return this.right_;
};
/**
* @enum {string}
*/
ol.expr.MathOp = {
ADD: '+',
SUBTRACT: '-',
MULTIPLY: '*',
DIVIDE: '/',
MOD: '%'
};
/**
* A math expression (e.g. `foo + 42`, `bar % 10`).
*
* @constructor
* @extends {ol.expr.Expression}
* @param {ol.expr.MathOp} operator Math operator.
* @param {ol.expr.Expression} left Left expression.
* @param {ol.expr.Expression} right Right expression.
*/
ol.expr.Math = function(operator, left, right) {
/**
* @type {ol.expr.MathOp}
* @private
*/
this.operator_ = operator;
/**
* @type {ol.expr.Expression}
* @private
*/
this.left_ = left;
/**
* @type {ol.expr.Expression}
* @private
*/
this.right_ = right;
};
goog.inherits(ol.expr.Math, ol.expr.Expression);
/**
* Determine if a given string is a valid math operator.
* @param {string} candidate Operator to test.
* @return {boolean} The operator is valid.
*/
ol.expr.Math.isValidOp = (function() {
var valid = {};
for (var key in ol.expr.MathOp) {
valid[ol.expr.MathOp[key]] = true;
}
return function isValidOp(candidate) {
return !!valid[candidate];
};
}());
/**
* @inheritDoc
*/
ol.expr.Math.prototype.evaluate = function(opt_scope, opt_fns, opt_this) {
var result;
var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this);
var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this);
/**
* TODO: throw if rightVal, leftVal not numbers - this would require the use
* of a concat function for strings but it would let us serialize these as
* math functions where available elsewhere
*/
var op = this.operator_;
if (op === ol.expr.MathOp.ADD) {
result = leftVal + rightVal;
} else if (op === ol.expr.MathOp.SUBTRACT) {
result = Number(leftVal) - Number(rightVal);
} else if (op === ol.expr.MathOp.MULTIPLY) {
result = Number(leftVal) * Number(rightVal);
} else if (op === ol.expr.MathOp.DIVIDE) {
result = Number(leftVal) / Number(rightVal);
} else if (op === ol.expr.MathOp.MOD) {
result = Number(leftVal) % Number(rightVal);
} else {
throw new Error('Unsupported math operator: ' + this.operator_);
}
return result;
};
/**
* Get the math operator.
* @return {string} The math operator.
*/
ol.expr.Math.prototype.getOperator = function() {
return this.operator_;
};
/**
* Get the left expression.
* @return {ol.expr.Expression} The left expression.
*/
ol.expr.Math.prototype.getLeft = function() {
return this.left_;
};
/**
* Get the right expression.
* @return {ol.expr.Expression} The right expression.
*/
ol.expr.Math.prototype.getRight = function() {
return this.right_;
};
/**
* A member expression (e.g. `foo.bar`).
*
* @constructor
* @extends {ol.expr.Expression}
* @param {ol.expr.Expression} object An expression that resolves to an
* object.
* @param {ol.expr.Identifier} property Identifier with name of property.
*/
ol.expr.Member = function(object, property) {
/**
* @type {ol.expr.Expression}
* @private
*/
this.object_ = object;
/**
* @type {ol.expr.Identifier}
* @private
*/
this.property_ = property;
};
goog.inherits(ol.expr.Member, ol.expr.Expression);
/**
* @inheritDoc
*/
ol.expr.Member.prototype.evaluate = function(opt_scope, opt_fns,
opt_this) {
var obj = this.object_.evaluate(opt_scope, opt_fns, opt_this);
if (!goog.isObject(obj)) {
throw new Error('Expected member expression to evaluate to an object ' +
'but got ' + obj);
}
return this.property_.evaluate(/** @type {Object} */ (obj));
};
/**
* Get the object expression.
* @return {ol.expr.Expression} The object.
*/
ol.expr.Member.prototype.getObject = function() {
return this.object_;
};
/**
* Get the property expression.
* @return {ol.expr.Identifier} The property.
*/
ol.expr.Member.prototype.getProperty = function() {
return this.property_;
};
/**
* A logical not expression (e.g. `!foo`).
*
* @constructor
* @extends {ol.expr.Expression}
* @param {ol.expr.Expression} argument Expression to negate.
*/
ol.expr.Not = function(argument) {
/**
* @type {ol.expr.Expression}
* @private
*/
this.argument_ = argument;
};
goog.inherits(ol.expr.Not, ol.expr.Expression);
/**
* @inheritDoc
*/
ol.expr.Not.prototype.evaluate = function(opt_scope, opt_fns, opt_this) {
return !this.argument_.evaluate(opt_scope, opt_fns, opt_this);
};
/**
* Get the argument (the negated expression).
* @return {ol.expr.Expression} The argument.
*/
ol.expr.Not.prototype.getArgument = function() {
return this.argument_;
};

900
src/ol/expr/lexer.js Normal file
View File

@@ -0,0 +1,900 @@
/**
* The logic and naming of methods here are inspired by Esprima (BSD Licensed).
* Esprima (http://esprima.org) includes the following copyright notices:
*
* Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com>
* Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com>
* Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
* Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be>
* Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
* Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com>
* Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
* Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
* Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
*/
goog.provide('ol.expr.Char'); // TODO: remove this - see #785
goog.provide('ol.expr.Lexer');
goog.provide('ol.expr.Token');
goog.provide('ol.expr.TokenType');
goog.provide('ol.expr.UnexpectedToken');
goog.require('goog.asserts');
goog.require('goog.debug.Error');
/**
* @enum {number}
*/
ol.expr.Char = {
AMPERSAND: 38,
BACKSLASH: 92,
BANG: 33, // !
CARRIAGE_RETURN: 13,
COMMA: 44,
DIGIT_0: 48,
DIGIT_7: 55,
DIGIT_9: 57,
DOLLAR: 36,
DOUBLE_QUOTE: 34,
DOT: 46,
EQUAL: 61,
FORM_FEED: 0xC,
GREATER: 62,
LEFT_PAREN: 40,
LESS: 60,
LINE_FEED: 10,
LINE_SEPARATOR: 0x2028,
LOWER_A: 97,
LOWER_E: 101,
LOWER_F: 102,
LOWER_X: 120,
LOWER_Z: 122,
MINUS: 45,
NONBREAKING_SPACE: 0xA0,
PARAGRAPH_SEPARATOR: 0x2029,
PERCENT: 37,
PIPE: 124,
PLUS: 43,
RIGHT_PAREN: 41,
SINGLE_QUOTE: 39,
SLASH: 47,
SPACE: 32,
STAR: 42,
TAB: 9,
TILDE: 126,
UNDERSCORE: 95,
UPPER_A: 65,
UPPER_E: 69,
UPPER_F: 70,
UPPER_X: 88,
UPPER_Z: 90,
VERTICAL_TAB: 0xB
};
/**
* @enum {string}
*/
ol.expr.TokenType = {
BOOLEAN_LITERAL: 'Boolean',
EOF: '<end>',
IDENTIFIER: 'Identifier',
KEYWORD: 'Keyword',
NULL_LITERAL: 'Null',
NUMERIC_LITERAL: 'Numeric',
PUNCTUATOR: 'Punctuator',
STRING_LITERAL: 'String',
UNKNOWN: 'Unknown'
};
/**
* @typedef {{type: (ol.expr.TokenType),
* value: (string|number|boolean|null),
* index: (number)}}
*/
ol.expr.Token;
/**
* Lexer constructor. Provides a tokenizer for a limited subset of ECMAScript
* 5.1 expressions (http://www.ecma-international.org/ecma-262/5.1/#sec-11).
*
* @constructor
* @param {string} source Source code.
*/
ol.expr.Lexer = function(source) {
/**
* Source code.
* @type {string}
* @private
*/
this.source_ = source;
/**
* Source length.
* @type {number}
* @private
*/
this.length_ = source.length;
/**
* Current character index.
* @type {number}
* @private
*/
this.index_ = 0;
/**
* Next character index (only set after `peek`ing).
* @type {number}
* @private
*/
this.nextIndex_ = 0;
};
/**
* Scan the next token and throw if it isn't a punctuator that matches input.
* @param {string} value Token value.
*/
ol.expr.Lexer.prototype.expect = function(value) {
var match = this.match(value);
if (!match) {
throw new ol.expr.UnexpectedToken({
type: ol.expr.TokenType.UNKNOWN,
value: this.getCurrentChar_(),
index: this.index_
});
}
this.skip();
};
/**
* Increment the current character index.
*
* @param {number} delta Delta by which the index is advanced.
* @private
*/
ol.expr.Lexer.prototype.increment_ = function(delta) {
this.index_ += delta;
};
/**
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3
*
* @param {number} code The unicode of a character.
* @return {boolean} The character is a decimal digit.
* @private
*/
ol.expr.Lexer.prototype.isDecimalDigit_ = function(code) {
return (
code >= ol.expr.Char.DIGIT_0 &&
code <= ol.expr.Char.DIGIT_9);
};
/**
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.2
*
* @param {string} id A string identifier.
* @return {boolean} The identifier is a future reserved word.
* @private
*/
ol.expr.Lexer.prototype.isFutureReservedWord_ = function(id) {
return (
id === 'class' ||
id === 'enum' ||
id === 'export' ||
id === 'extends' ||
id === 'import' ||
id === 'super');
};
/**
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3
*
* @param {number} code The unicode of a character.
* @return {boolean} The character is a hex digit.
* @private
*/
ol.expr.Lexer.prototype.isHexDigit_ = function(code) {
return this.isDecimalDigit_(code) ||
(code >= ol.expr.Char.LOWER_A &&
code <= ol.expr.Char.LOWER_F) ||
(code >= ol.expr.Char.UPPER_A &&
code <= ol.expr.Char.UPPER_F);
};
/**
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.6
* Doesn't deal with non-ascii identifiers.
*
* @param {number} code The unicode of a character.
* @return {boolean} The character is a valid identifier part.
* @private
*/
ol.expr.Lexer.prototype.isIdentifierPart_ = function(code) {
return this.isIdentifierStart_(code) ||
(code >= ol.expr.Char.DIGIT_0 &&
code <= ol.expr.Char.DIGIT_9);
};
/**
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.6
* Doesn't yet deal with non-ascii identifiers.
*
* @param {number} code The unicode of a character.
* @return {boolean} The character is a valid identifier start.
* @private
*/
ol.expr.Lexer.prototype.isIdentifierStart_ = function(code) {
return (code === ol.expr.Char.DOLLAR) ||
(code === ol.expr.Char.UNDERSCORE) ||
(code >= ol.expr.Char.UPPER_A &&
code <= ol.expr.Char.UPPER_Z) ||
(code >= ol.expr.Char.LOWER_A &&
code <= ol.expr.Char.LOWER_Z);
};
/**
* Determine if the given identifier is an ECMAScript keyword. These cannot
* be used as identifiers in programs. There is no real reason these could not
* be used in ol.exprs - but they are reserved for future use.
*
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.1
*
* @param {string} id Identifier.
* @return {boolean} The identifier is a keyword.
* @private
*/
ol.expr.Lexer.prototype.isKeyword_ = function(id) {
return (
id === 'break' ||
id === 'case' ||
id === 'catch' ||
id === 'continue' ||
id === 'debugger' ||
id === 'default' ||
id === 'delete' ||
id === 'do' ||
id === 'else' ||
id === 'finally' ||
id === 'for' ||
id === 'function' ||
id === 'if' ||
id === 'in' ||
id === 'instanceof' ||
id === 'new' ||
id === 'return' ||
id === 'switch' ||
id === 'this' ||
id === 'throw' ||
id === 'try' ||
id === 'typeof' ||
id === 'var' ||
id === 'void' ||
id === 'while' ||
id === 'with');
};
/**
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.3
*
* @param {number} code The unicode of a character.
* @return {boolean} The character is a line terminator.
* @private
*/
ol.expr.Lexer.prototype.isLineTerminator_ = function(code) {
return (code === ol.expr.Char.LINE_FEED) ||
(code === ol.expr.Char.CARRIAGE_RETURN) ||
(code === ol.expr.Char.LINE_SEPARATOR) ||
(code === ol.expr.Char.PARAGRAPH_SEPARATOR);
};
/**
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3
*
* @param {number} code The unicode of a character.
* @return {boolean} The character is an octal digit.
* @private
*/
ol.expr.Lexer.prototype.isOctalDigit_ = function(code) {
return (
code >= ol.expr.Char.DIGIT_0 &&
code <= ol.expr.Char.DIGIT_7);
};
/**
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.2
*
* @param {number} code The unicode of a character.
* @return {boolean} The character is whitespace.
* @private
*/
ol.expr.Lexer.prototype.isWhitespace_ = function(code) {
return (code === ol.expr.Char.SPACE) ||
(code === ol.expr.Char.TAB) ||
(code === ol.expr.Char.VERTICAL_TAB) ||
(code === ol.expr.Char.FORM_FEED) ||
(code === ol.expr.Char.NONBREAKING_SPACE) ||
(code >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005' +
'\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'
.indexOf(String.fromCharCode(code)) > 0);
};
/**
* Get the unicode of the character at the given offset from the current index.
*
* @param {number} delta Offset from current index.
* @return {number} The character code.
* @private
*/
ol.expr.Lexer.prototype.getCharCode_ = function(delta) {
return this.source_.charCodeAt(this.index_ + delta);
};
/**
* Get the character at the current index.
*
* @return {string} The current character.
* @private
*/
ol.expr.Lexer.prototype.getCurrentChar_ = function() {
return this.source_[this.index_];
};
/**
* Get the unicode of the character at the current index.
*
* @return {number} The current character code.
* @private
*/
ol.expr.Lexer.prototype.getCurrentCharCode_ = function() {
return this.getCharCode_(0);
};
/**
* Determine whether the upcoming token matches the given punctuator.
* @param {string} value Punctuator value.
* @return {boolean} The token matches.
*/
ol.expr.Lexer.prototype.match = function(value) {
var token = this.peek();
return (
token.type === ol.expr.TokenType.PUNCTUATOR &&
token.value === value);
};
/**
* Scan the next token.
*
* @return {ol.expr.Token} Next token.
*/
ol.expr.Lexer.prototype.next = function() {
var code = this.skipWhitespace_();
if (this.index_ >= this.length_) {
return {
type: ol.expr.TokenType.EOF,
value: null,
index: this.index_
};
}
// check for common punctuation
if (code === ol.expr.Char.LEFT_PAREN ||
code === ol.expr.Char.RIGHT_PAREN) {
return this.scanPunctuator_(code);
}
// check for string literal
if (code === ol.expr.Char.SINGLE_QUOTE ||
code === ol.expr.Char.DOUBLE_QUOTE) {
return this.scanStringLiteral_(code);
}
// check for identifier
if (this.isIdentifierStart_(code)) {
return this.scanIdentifier_(code);
}
// check dot punctuation or decimal
if (code === ol.expr.Char.DOT) {
if (this.isDecimalDigit_(this.getCharCode_(1))) {
return this.scanNumericLiteral_(code);
}
return this.scanPunctuator_(code);
}
// check for numeric literal
if (this.isDecimalDigit_(code)) {
return this.scanNumericLiteral_(code);
}
// all the rest is punctuation
return this.scanPunctuator_(code);
};
/**
* Peek at the next token, but don't advance the index.
*
* @return {ol.expr.Token} The upcoming token.
*/
ol.expr.Lexer.prototype.peek = function() {
var currentIndex = this.index_;
var token = this.next();
this.nextIndex_ = this.index_;
this.index_ = currentIndex;
return token;
};
/**
* Scan hex literal as numeric token.
*
* @param {number} code The current character code.
* @return {ol.expr.Token} Numeric literal token.
* @private
*/
ol.expr.Lexer.prototype.scanHexLiteral_ = function(code) {
var str = '';
var start = this.index_ - 2;
while (this.index_ < this.length_) {
if (!this.isHexDigit_(code)) {
break;
}
str += String.fromCharCode(code);
this.increment_(1);
code = this.getCurrentCharCode_();
}
if (str.length === 0 || this.isIdentifierStart_(code)) {
throw new ol.expr.UnexpectedToken({
type: ol.expr.TokenType.UNKNOWN,
value: String.fromCharCode(code),
index: this.index_
});
}
goog.asserts.assert(!isNaN(parseInt('0x' + str, 16)), 'Valid hex: ' + str);
return {
type: ol.expr.TokenType.NUMERIC_LITERAL,
value: parseInt('0x' + str, 16),
index: start
};
};
/**
* Scan identifier token.
*
* @param {number} code The current character code.
* @return {ol.expr.Token} Identifier token.
* @private
*/
ol.expr.Lexer.prototype.scanIdentifier_ = function(code) {
goog.asserts.assert(this.isIdentifierStart_(code),
'Must be called with a valid identifier');
var start = this.index_;
this.increment_(1);
while (this.index_ < this.length_) {
code = this.getCurrentCharCode_();
if (this.isIdentifierPart_(code)) {
this.increment_(1);
} else {
break;
}
}
var id = this.source_.slice(start, this.index_);
var type;
if (id.length === 1) {
type = ol.expr.TokenType.IDENTIFIER;
} else if (this.isKeyword_(id)) {
type = ol.expr.TokenType.KEYWORD;
} else if (id === 'null') {
type = ol.expr.TokenType.NULL_LITERAL;
} else if (id === 'true' || id === 'false') {
type = ol.expr.TokenType.BOOLEAN_LITERAL;
} else {
type = ol.expr.TokenType.IDENTIFIER;
}
return {
type: type,
value: id,
index: start
};
};
/**
* Scan numeric literal token.
* http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3
*
* @param {number} code The current character code.
* @return {ol.expr.Token} Numeric literal token.
* @private
*/
ol.expr.Lexer.prototype.scanNumericLiteral_ = function(code) {
goog.asserts.assert(
code === ol.expr.Char.DOT || this.isDecimalDigit_(code),
'Valid start for numeric literal: ' + String.fromCharCode(code));
// start assembling numeric string
var str = '';
var start = this.index_;
if (code !== ol.expr.Char.DOT) {
if (code === ol.expr.Char.DIGIT_0) {
var nextCode = this.getCharCode_(1);
// hex literals start with 0X or 0x
if (nextCode === ol.expr.Char.UPPER_X ||
nextCode === ol.expr.Char.LOWER_X) {
this.increment_(2);
return this.scanHexLiteral_(this.getCurrentCharCode_());
}
// octals start with 0
if (this.isOctalDigit_(nextCode)) {
this.increment_(1);
return this.scanOctalLiteral_(nextCode);
}
// numbers like 09 not allowed
if (this.isDecimalDigit_(nextCode)) {
throw new ol.expr.UnexpectedToken({
type: ol.expr.TokenType.UNKNOWN,
value: String.fromCharCode(nextCode),
index: this.index_
});
}
}
// scan all decimal chars
while (this.isDecimalDigit_(code)) {
str += String.fromCharCode(code);
this.increment_(1);
code = this.getCurrentCharCode_();
}
}
// scan fractional part
if (code === ol.expr.Char.DOT) {
str += String.fromCharCode(code);
this.increment_(1);
code = this.getCurrentCharCode_();
// scan all decimal chars
while (this.isDecimalDigit_(code)) {
str += String.fromCharCode(code);
this.increment_(1);
code = this.getCurrentCharCode_();
}
}
// scan exponent
if (code === ol.expr.Char.UPPER_E ||
code === ol.expr.Char.LOWER_E) {
str += 'E';
this.increment_(1);
code = this.getCurrentCharCode_();
if (code === ol.expr.Char.PLUS ||
code === ol.expr.Char.MINUS) {
str += String.fromCharCode(code);
this.increment_(1);
code = this.getCurrentCharCode_();
}
if (!this.isDecimalDigit_(code)) {
throw new ol.expr.UnexpectedToken({
type: ol.expr.TokenType.UNKNOWN,
value: String.fromCharCode(code),
index: this.index_
});
}
// scan all decimal chars (TODO: unduplicate this)
while (this.isDecimalDigit_(code)) {
str += String.fromCharCode(code);
this.increment_(1);
code = this.getCurrentCharCode_();
}
}
if (this.isIdentifierStart_(code)) {
throw new ol.expr.UnexpectedToken({
type: ol.expr.TokenType.UNKNOWN,
value: String.fromCharCode(code),
index: this.index_
});
}
goog.asserts.assert(!isNaN(parseFloat(str)), 'Valid number: ' + str);
return {
type: ol.expr.TokenType.NUMERIC_LITERAL,
value: parseFloat(str),
index: start
};
};
/**
* Scan octal literal as numeric token.
*
* @param {number} code The current character code.
* @return {ol.expr.Token} Numeric literal token.
* @private
*/
ol.expr.Lexer.prototype.scanOctalLiteral_ = function(code) {
goog.asserts.assert(this.isOctalDigit_(code));
var str = '0' + String.fromCharCode(code);
var start = this.index_ - 1;
this.increment_(1);
while (this.index_ < this.length_) {
code = this.getCurrentCharCode_();
if (!this.isOctalDigit_(code)) {
break;
}
str += String.fromCharCode(code);
this.increment_(1);
}
code = this.getCurrentCharCode_();
if (this.isIdentifierStart_(code) ||
this.isDecimalDigit_(code)) {
throw new ol.expr.UnexpectedToken({
type: ol.expr.TokenType.UNKNOWN,
value: String.fromCharCode(code),
index: this.index_
});
}
goog.asserts.assert(!isNaN(parseInt(str, 8)), 'Valid octal: ' + str);
return {
type: ol.expr.TokenType.NUMERIC_LITERAL,
value: parseInt(str, 8),
index: start
};
};
/**
* Scan punctuator token (a subset of allowed tokens in 7.7).
*
* @param {number} code The current character code.
* @return {ol.expr.Token} Punctuator token.
* @private
*/
ol.expr.Lexer.prototype.scanPunctuator_ = function(code) {
var start = this.index_;
// single char punctuation that also doesn't start longer punctuation
// (we disallow assignment, so no += etc.)
if (code === ol.expr.Char.DOT ||
code === ol.expr.Char.LEFT_PAREN ||
code === ol.expr.Char.RIGHT_PAREN ||
code === ol.expr.Char.COMMA ||
code === ol.expr.Char.PLUS ||
code === ol.expr.Char.MINUS ||
code === ol.expr.Char.STAR ||
code === ol.expr.Char.SLASH ||
code === ol.expr.Char.PERCENT ||
code === ol.expr.Char.TILDE) {
this.increment_(1);
return {
type: ol.expr.TokenType.PUNCTUATOR,
value: String.fromCharCode(code),
index: start
};
}
// check for 2-character punctuation
var nextCode = this.getCharCode_(1);
// assignment or comparison (and we don't allow assignment)
if (nextCode === ol.expr.Char.EQUAL) {
if (code === ol.expr.Char.BANG || code === ol.expr.Char.EQUAL) {
// we're looking at !=, ==, !==, or ===
this.increment_(2);
// check for triple
if (this.getCurrentCharCode_() === ol.expr.Char.EQUAL) {
this.increment_(1);
return {
type: ol.expr.TokenType.PUNCTUATOR,
value: String.fromCharCode(code) + '==',
index: start
};
} else {
// != or ==
return {
type: ol.expr.TokenType.PUNCTUATOR,
value: String.fromCharCode(code) + '=',
index: start
};
}
}
if (code === ol.expr.Char.GREATER ||
code === ol.expr.Char.LESS) {
this.increment_(2);
return {
type: ol.expr.TokenType.PUNCTUATOR,
value: String.fromCharCode(code) + '=',
index: start
};
}
}
// remaining 2-charcter punctuators are || and &&
if (code === nextCode &&
(code === ol.expr.Char.PIPE ||
code === ol.expr.Char.AMPERSAND)) {
this.increment_(2);
var str = String.fromCharCode(code);
return {
type: ol.expr.TokenType.PUNCTUATOR,
value: str + str,
index: start
};
}
// we don't allow 4-character punctuator (>>>=)
// and the allowed 3-character punctuators (!==, ===) are already consumed
// other single character punctuators
if (code === ol.expr.Char.GREATER ||
code === ol.expr.Char.LESS ||
code === ol.expr.Char.BANG ||
code === ol.expr.Char.AMPERSAND ||
code === ol.expr.Char.PIPE) {
this.increment_(1);
return {
type: ol.expr.TokenType.PUNCTUATOR,
value: String.fromCharCode(code),
index: start
};
}
throw new ol.expr.UnexpectedToken({
type: ol.expr.TokenType.UNKNOWN,
value: String.fromCharCode(code),
index: this.index_
});
};
/**
* Scan string literal token.
*
* @param {number} quote The current character code.
* @return {ol.expr.Token} String literal token.
* @private
*/
ol.expr.Lexer.prototype.scanStringLiteral_ = function(quote) {
goog.asserts.assert(quote === ol.expr.Char.SINGLE_QUOTE ||
quote === ol.expr.Char.DOUBLE_QUOTE,
'Strings must start with a quote: ' + String.fromCharCode(quote));
var start = this.index_;
this.increment_(1);
var str = '';
var code;
while (this.index_ < this.length_) {
code = this.getCurrentCharCode_();
this.increment_(1);
if (code === quote) {
quote = 0;
break;
}
// look for escaped quote or backslash
if (code === ol.expr.Char.BACKSLASH) {
str += this.getCurrentChar_();
this.increment_(1);
} else {
str += String.fromCharCode(code);
}
}
if (quote !== 0) {
// unterminated string literal
throw new ol.expr.UnexpectedToken(this.peek());
}
return {
type: ol.expr.TokenType.STRING_LITERAL,
value: str,
index: start
};
};
/**
* After peeking, skip may be called to advance the cursor without re-scanning.
*/
ol.expr.Lexer.prototype.skip = function() {
this.index_ = this.nextIndex_;
};
/**
* Skip all whitespace.
* @return {number} The character code of the first non-whitespace character.
* @private
*/
ol.expr.Lexer.prototype.skipWhitespace_ = function() {
var code = NaN;
while (this.index_ < this.length_) {
code = this.getCurrentCharCode_();
if (this.isWhitespace_(code)) {
this.increment_(1);
} else {
break;
}
}
return code;
};
/**
* Error object for unexpected tokens.
* @param {ol.expr.Token} token The unexpected token.
* @param {string=} opt_message Custom error message.
* @constructor
* @extends {goog.debug.Error}
*/
ol.expr.UnexpectedToken = function(token, opt_message) {
var message = goog.isDef(opt_message) ? opt_message :
'Unexpected token ' + token.value + ' at index ' + token.index;
goog.debug.Error.call(this, message);
/**
* @type {ol.expr.Token}
*/
this.token = token;
};
goog.inherits(ol.expr.UnexpectedToken, goog.debug.Error);
/** @override */
ol.expr.UnexpectedToken.prototype.name = 'UnexpectedToken';

478
src/ol/expr/parser.js Normal file
View File

@@ -0,0 +1,478 @@
/**
* The logic and naming of methods here are inspired by Esprima (BSD Licensed).
* Esprima (http://esprima.org) includes the following copyright notices:
*
* Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com>
* Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com>
* Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
* Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be>
* Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
* Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com>
* Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
* Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
* Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
*/
goog.provide('ol.expr.Parser');
goog.require('goog.asserts');
goog.require('ol.expr.Call');
goog.require('ol.expr.Comparison');
goog.require('ol.expr.ComparisonOp');
goog.require('ol.expr.Expression');
goog.require('ol.expr.Identifier');
goog.require('ol.expr.Lexer');
goog.require('ol.expr.Literal');
goog.require('ol.expr.Logical');
goog.require('ol.expr.LogicalOp');
goog.require('ol.expr.Math');
goog.require('ol.expr.MathOp');
goog.require('ol.expr.Member');
goog.require('ol.expr.Not');
goog.require('ol.expr.Token');
goog.require('ol.expr.TokenType');
goog.require('ol.expr.UnexpectedToken');
/**
* Instances of ol.expr.Parser parse a very limited set of ECMAScript
* expressions (http://www.ecma-international.org/ecma-262/5.1/#sec-11).
*
* - Primary Expression (11.1):
* - Identifier (e.g. `foo`)
* - Literal (e.g. `"some string"` or `42`)
* - Grouped (e.g. `(foo)`)
* - Left-Hand-Side Expression (11.2):
* - Property Accessors
* - Dot notation only
* - Function Calls
* - Identifier with arguments only (e.g. `foo(bar, 42)`)
* - Unary Operators (11.4)
* - Logical Not (e.g. `!foo`)
* - Multiplicitave Operators (11.5)
* - Additive Operators (11.6)
* - Relational Operators (11.8)
* - <, >, <=, and >= only
* - Equality Operators (11.9)
* - Binary Logical Operators (11.11)
*
* @constructor
*/
ol.expr.Parser = function() {
};
/**
* Determine the precedence for the given token.
*
* @param {ol.expr.Token} token A token.
* @return {number} The precedence for the given token. Higher gets more
* precedence.
* @private
*/
ol.expr.Parser.prototype.binaryPrecedence_ = function(token) {
var precedence = 0;
if (token.type !== ol.expr.TokenType.PUNCTUATOR) {
return precedence;
}
switch (token.value) {
case ol.expr.LogicalOp.OR:
precedence = 1;
break;
case ol.expr.LogicalOp.AND:
precedence = 2;
break;
case ol.expr.ComparisonOp.EQ:
case ol.expr.ComparisonOp.NEQ:
case ol.expr.ComparisonOp.STRICT_EQ:
case ol.expr.ComparisonOp.STRICT_NEQ:
precedence = 3;
break;
case ol.expr.ComparisonOp.GT:
case ol.expr.ComparisonOp.LT:
case ol.expr.ComparisonOp.GTE:
case ol.expr.ComparisonOp.LTE:
precedence = 4;
break;
case ol.expr.MathOp.ADD:
case ol.expr.MathOp.SUBTRACT:
precedence = 5;
break;
case ol.expr.MathOp.MULTIPLY:
case ol.expr.MathOp.DIVIDE:
case ol.expr.MathOp.MOD:
precedence = 6;
break;
default:
// punctuator is not a supported binary operator, that's fine
break;
}
return precedence;
};
/**
* Create a binary expression.
*
* @param {string} operator Operator.
* @param {ol.expr.Expression} left Left expression.
* @param {ol.expr.Expression} right Right expression.
* @return {ol.expr.Expression} The expression.
* @private
*/
ol.expr.Parser.prototype.createBinaryExpression_ = function(operator,
left, right) {
var expr;
if (ol.expr.Comparison.isValidOp(operator)) {
expr = new ol.expr.Comparison(
/** @type {ol.expr.ComparisonOp.<string>} */ (operator),
left, right);
} else if (ol.expr.Logical.isValidOp(operator)) {
expr = new ol.expr.Logical(
/** @type {ol.expr.LogicalOp.<string>} */ (operator),
left, right);
} else if (ol.expr.Math.isValidOp(operator)) {
expr = new ol.expr.Math(
/** @type {ol.expr.MathOp.<string>} */ (operator),
left, right);
} else {
throw new Error('Unsupported binary operator: ' + operator);
}
return expr;
};
/**
* Create a call expression.
*
* @param {ol.expr.Expression} callee Expression for function.
* @param {Array.<ol.expr.Expression>} args Arguments array.
* @return {ol.expr.Call} Call expression.
* @private
*/
ol.expr.Parser.prototype.createCallExpression_ = function(callee, args) {
return new ol.expr.Call(callee, args);
};
/**
* Create an identifier expression.
*
* @param {string} name Identifier name.
* @return {ol.expr.Identifier} Identifier expression.
* @private
*/
ol.expr.Parser.prototype.createIdentifier_ = function(name) {
return new ol.expr.Identifier(name);
};
/**
* Create a literal expression.
*
* @param {string|number|boolean|null} value Literal value.
* @return {ol.expr.Literal} The literal expression.
* @private
*/
ol.expr.Parser.prototype.createLiteral_ = function(value) {
return new ol.expr.Literal(value);
};
/**
* Create a member expression.
*
* // TODO: make exp {ol.expr.Member|ol.expr.Identifier}
* @param {ol.expr.Expression} object Expression.
* @param {ol.expr.Identifier} property Member name.
* @return {ol.expr.Member} The member expression.
* @private
*/
ol.expr.Parser.prototype.createMemberExpression_ = function(object,
property) {
return new ol.expr.Member(object, property);
};
/**
* Create a unary expression. The only true unary operator supported here is
* "!". For +/-, we apply the operator to literal expressions and return
* another literal.
*
* @param {ol.expr.Token} op Operator.
* @param {ol.expr.Expression} argument Expression.
* @return {ol.expr.Expression} The unary expression.
* @private
*/
ol.expr.Parser.prototype.createUnaryExpression_ = function(op, argument) {
goog.asserts.assert(op.value === '!' || op.value === '+' || op.value === '-');
var expr;
if (op.value === '!') {
expr = new ol.expr.Not(argument);
} else if (!(argument instanceof ol.expr.Literal)) {
throw new ol.expr.UnexpectedToken(op);
} else {
// we've got +/- literal
if (op.value === '+') {
expr = this.createLiteral_(
+ /** @type {number|string|boolean|null} */ (argument.evaluate()));
} else {
expr = this.createLiteral_(
- /** @type {number|string|boolean|null} */ (argument.evaluate()));
}
}
return expr;
};
/**
* Parse an expression.
*
* @param {string} source Expression source.
* @return {ol.expr.Expression} Expression.
*/
ol.expr.Parser.prototype.parse = function(source) {
var lexer = new ol.expr.Lexer(source);
var expr = this.parseExpression_(lexer);
var token = lexer.peek();
if (token.type !== ol.expr.TokenType.EOF) {
throw new ol.expr.UnexpectedToken(token);
}
return expr;
};
/**
* Parse call arguments
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.4
*
* @param {ol.expr.Lexer} lexer Lexer.
* @return {Array.<ol.expr.Expression>} Arguments.
* @private
*/
ol.expr.Parser.prototype.parseArguments_ = function(lexer) {
var args = [];
lexer.expect('(');
if (!lexer.match(')')) {
while (true) {
args.push(this.parseBinaryExpression_(lexer));
if (lexer.match(')')) {
break;
}
lexer.expect(',');
}
}
lexer.skip();
return args;
};
/**
* Parse a binary expression. Supported binary expressions:
*
* - Multiplicative Operators (`*`, `/`, `%`)
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.5
* - Additive Operators (`+`, `-`)
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.6
*
* - Relational Operators (`<`, `>`, `<=`, `>=`)
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.8
*
* - Equality Operators (`==`, `!=`, `===`, `!==`)
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.9
*
* - Binary Logical Operators (`&&`, `||`)
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.11
*
* @param {ol.expr.Lexer} lexer Lexer.
* @return {ol.expr.Expression} Expression.
* @private
*/
ol.expr.Parser.prototype.parseBinaryExpression_ = function(lexer) {
var left = this.parseUnaryExpression_(lexer);
var operator = lexer.peek();
var precedence = this.binaryPrecedence_(operator);
if (precedence === 0) {
// not a supported binary operator
return left;
}
lexer.skip();
var right = this.parseUnaryExpression_(lexer);
var stack = [left, operator, right];
precedence = this.binaryPrecedence_(lexer.peek());
while (precedence > 0) {
// TODO: cache operator precedence in stack
while (stack.length > 2 &&
(precedence <= this.binaryPrecedence_(stack[stack.length - 2]))) {
right = stack.pop();
operator = stack.pop();
left = stack.pop();
stack.push(this.createBinaryExpression_(operator.value, left, right));
}
stack.push(lexer.next());
stack.push(this.parseUnaryExpression_(lexer));
precedence = this.binaryPrecedence_(lexer.peek());
}
var i = stack.length - 1;
var expr = stack[i];
while (i > 1) {
expr = this.createBinaryExpression_(stack[i - 1].value, stack[i - 2], expr);
i -= 2;
}
return expr;
};
/**
* Parse a group expression.
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.1.6
*
* @param {ol.expr.Lexer} lexer Lexer.
* @return {ol.expr.Expression} Expression.
* @private
*/
ol.expr.Parser.prototype.parseGroupExpression_ = function(lexer) {
lexer.expect('(');
var expr = this.parseExpression_(lexer);
lexer.expect(')');
return expr;
};
/**
* Parse left-hand-side expression. Limited to Member Expressions
* and Call Expressions.
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.2
*
* @param {ol.expr.Lexer} lexer Lexer.
* @return {ol.expr.Expression} Expression.
* @private
*/
ol.expr.Parser.prototype.parseLeftHandSideExpression_ = function(lexer) {
var expr = this.parsePrimaryExpression_(lexer);
var token = lexer.peek();
if (token.value === '(') {
// only allow calls on identifiers (e.g. `foo()` not `foo.bar()`)
if (!(expr instanceof ol.expr.Identifier)) {
// TODO: more helpful error messages for restricted syntax
throw new ol.expr.UnexpectedToken(token);
}
var args = this.parseArguments_(lexer);
expr = this.createCallExpression_(expr, args);
} else {
// TODO: throw if not Identifier
while (token.value === '.') {
var property = this.parseNonComputedMember_(lexer);
expr = this.createMemberExpression_(expr, property);
token = lexer.peek();
}
}
return expr;
};
/**
* Parse non-computed member.
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.2
*
* @param {ol.expr.Lexer} lexer Lexer.
* @return {ol.expr.Identifier} Expression.
* @private
*/
ol.expr.Parser.prototype.parseNonComputedMember_ = function(lexer) {
lexer.expect('.');
var token = lexer.next();
if (token.type !== ol.expr.TokenType.IDENTIFIER &&
token.type !== ol.expr.TokenType.KEYWORD &&
token.type !== ol.expr.TokenType.BOOLEAN_LITERAL &&
token.type !== ol.expr.TokenType.NULL_LITERAL) {
throw new ol.expr.UnexpectedToken(token);
}
return this.createIdentifier_(String(token.value));
};
/**
* Parse primary expression.
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.1
*
* @param {ol.expr.Lexer} lexer Lexer.
* @return {ol.expr.Expression} Expression.
* @private
*/
ol.expr.Parser.prototype.parsePrimaryExpression_ = function(lexer) {
var token = lexer.peek();
if (token.value === '(') {
return this.parseGroupExpression_(lexer);
}
lexer.skip();
var expr;
var type = token.type;
if (type === ol.expr.TokenType.IDENTIFIER) {
expr = this.createIdentifier_(/** @type {string} */ (token.value));
} else if (type === ol.expr.TokenType.STRING_LITERAL ||
type === ol.expr.TokenType.NUMERIC_LITERAL) {
// numeric and string literals are already the correct type
expr = this.createLiteral_(token.value);
} else if (type === ol.expr.TokenType.BOOLEAN_LITERAL) {
// because booleans are valid member properties, tokens are still string
expr = this.createLiteral_(token.value === 'true');
} else if (type === ol.expr.TokenType.NULL_LITERAL) {
expr = this.createLiteral_(null);
} else {
throw new ol.expr.UnexpectedToken(token);
}
return expr;
};
/**
* Parse expression with a unary operator. Limited to logical not operator.
* http://www.ecma-international.org/ecma-262/5.1/#sec-11.4
*
* @param {ol.expr.Lexer} lexer Lexer.
* @return {ol.expr.Expression} Expression.
* @private
*/
ol.expr.Parser.prototype.parseUnaryExpression_ = function(lexer) {
var expr;
var operator = lexer.peek();
if (operator.type !== ol.expr.TokenType.PUNCTUATOR) {
expr = this.parseLeftHandSideExpression_(lexer);
} else if (operator.value === '!' || operator.value === '-' ||
operator.value === '+') {
lexer.skip();
expr = this.parseUnaryExpression_(lexer);
expr = this.createUnaryExpression_(operator, expr);
} else {
expr = this.parseLeftHandSideExpression_(lexer);
}
return expr;
};
/**
* Parse an expression.
*
* @param {ol.expr.Lexer} lexer Lexer.
* @return {ol.expr.Expression} Expression.
* @private
*/
ol.expr.Parser.prototype.parseExpression_ = function(lexer) {
return this.parseBinaryExpression_(lexer);
};

View File

@@ -1 +0,0 @@
@exportSymbol ol.Expression

View File

@@ -1,80 +0,0 @@
goog.provide('ol.Expression');
goog.provide('ol.ExpressionLiteral');
/**
* Create a new expression. Expressions are used for instance to bind
* symbolizer properties to feature attributes.
*
* Example:
*
* // take the color from the color attribute
* color: new ol.Expression('color');
* // take the strokeWidth from the width attribute and multiply by 2.
* strokeWidth: new ol.Expression('width*2');
*
* @constructor
* @param {string} source Expression to be evaluated.
*/
ol.Expression = function(source) {
/**
* @type {string}
* @private
*/
this.source_ = source;
};
/**
* Evaluate the expression and return the result.
*
* @param {Object=} opt_thisArg Object to use as this when evaluating the
* expression. If not provided, the global object will be used.
* @param {Object=} opt_scope Evaluation scope. All properties of this object
* will be available as variables when evaluating the expression. If not
* provided, the global object will be used.
* @return {*} Result of the expression.
*/
ol.Expression.prototype.evaluate = function(opt_thisArg, opt_scope) {
var thisArg = goog.isDef(opt_thisArg) ? opt_thisArg : goog.global,
scope = goog.isDef(opt_scope) ? opt_scope : goog.global,
names = [],
values = [];
for (var name in scope) {
names.push(name);
values.push(scope[name]);
}
var evaluator = new Function(names.join(','), 'return ' + this.source_);
return evaluator.apply(thisArg, values);
};
/**
* @constructor
* @extends {ol.Expression}
* @param {*} value Literal value.
*/
ol.ExpressionLiteral = function(value) {
/**
* @type {*}
* @private
*/
this.value_ = value;
};
goog.inherits(ol.ExpressionLiteral, ol.Expression);
/**
* @inheritDoc
*/
ol.ExpressionLiteral.prototype.evaluate = function(opt_thisArg, opt_scope) {
return this.value_;
};

View File

@@ -1,7 +0,0 @@
@exportSymbol ol.filter.Filter
@exportSymbol ol.filter.Geometry
@exportSymbol ol.filter.Logical
@exportSymbol ol.filter.LogicalOperator
@exportProperty ol.filter.LogicalOperator.AND
@exportProperty ol.filter.LogicalOperator.OR

View File

@@ -1,3 +0,0 @@
/**
* @namespace ol.filter
*/

View File

@@ -1,39 +0,0 @@
goog.provide('ol.filter.Extent');
goog.require('ol.extent');
goog.require('ol.filter.Filter');
/**
* @constructor
* @extends {ol.filter.Filter}
* @param {ol.Extent} extent The extent.
*/
ol.filter.Extent = function(extent) {
goog.base(this);
/**
* @type {ol.Extent}
* @private
*/
this.extent_ = extent;
};
goog.inherits(ol.filter.Extent, ol.filter.Filter);
/**
* @return {ol.Extent} The filter extent.
*/
ol.filter.Extent.prototype.getExtent = function() {
return this.extent_;
};
/**
* @inheritDoc
*/
ol.filter.Extent.prototype.applies = function(feature) {
return ol.extent.intersects(feature.getGeometry().getBounds(), this.extent_);
};

View File

@@ -1,36 +0,0 @@
goog.provide('ol.filter.Filter');
goog.require('ol.Feature');
/**
* Create a new filter which can be used to filter features based on a
* function.
*
* Example:
*
* new ol.style.Rule({
* filter: new ol.filter.Filter(function(feature) {
* return feature.get('where') == 'outer';
* }),
* symbolizers: [
* ...
*
* @constructor
* @param {function(this:ol.filter.Filter, ol.Feature)=} opt_filterFunction
* Filter function. Should return true if the passed feature passes the
* filter, false otherwise.
*/
ol.filter.Filter = function(opt_filterFunction) {
if (goog.isDef(opt_filterFunction)) {
this.applies = opt_filterFunction;
}
};
/**
* @param {ol.Feature} feature Feature to evaluate the filter against.
* @return {boolean} The provided feature passes this filter.
*/
ol.filter.Filter.prototype.applies = goog.abstractMethod;

View File

@@ -1,42 +0,0 @@
goog.provide('ol.filter.Geometry');
goog.provide('ol.filter.GeometryType');
goog.require('ol.filter.Filter');
goog.require('ol.geom.GeometryType');
/**
* Filter features by geometry type.
* @constructor
* @extends {ol.filter.Filter}
* @param {ol.geom.GeometryType} type The geometry type.
*/
ol.filter.Geometry = function(type) {
goog.base(this);
/**
* @type {ol.geom.GeometryType}
* @private
*/
this.type_ = type;
};
goog.inherits(ol.filter.Geometry, ol.filter.Filter);
/**
* @inheritDoc
*/
ol.filter.Geometry.prototype.applies = function(feature) {
var geometry = feature.getGeometry();
return goog.isNull(geometry) ? false : geometry.getType() === this.type_;
};
/**
* @return {ol.geom.GeometryType} The geometry type.
*/
ol.filter.Geometry.prototype.getType = function() {
return this.type_;
};

View File

@@ -1,128 +0,0 @@
goog.provide('ol.filter.Logical');
goog.provide('ol.filter.LogicalOperator');
goog.provide('ol.filter.and');
goog.provide('ol.filter.not');
goog.provide('ol.filter.or');
goog.require('goog.asserts');
goog.require('ol.filter.Filter');
/**
* A filter to group (a) subfilter(s) by a logical operator (AND/OR/NOT).
*
* Example:
*
* var f1 = new ol.filter.Filter(myFunc1);
* var f2 = new ol.filter.Filter(myFunc2);
* // match one of the two filters above
* var logical = new ol.filter.Logical([f1, f2],
* ol.filter.LogicalOperator.OR);
*
* @constructor
* @extends {ol.filter.Filter}
* @param {Array.<ol.filter.Filter>} filters Filters to combine.
* @param {ol.filter.LogicalOperator} operator Operator.
*/
ol.filter.Logical = function(filters, operator) {
goog.base(this);
/**
* @type {Array.<ol.filter.Filter>}
* @private
*/
this.filters_ = filters;
goog.asserts.assert(filters.length > 0, 'Must supply at least one filter');
/**
* @type {ol.filter.LogicalOperator}
*/
this.operator = operator;
};
goog.inherits(ol.filter.Logical, ol.filter.Filter);
/**
* @inheritDoc
*/
ol.filter.Logical.prototype.applies = function(feature) {
var filters = this.filters_,
i = 0, ii = filters.length,
result;
switch (this.operator) {
case ol.filter.LogicalOperator.AND:
result = true;
while (result && i < ii) {
result = result && filters[i].applies(feature);
++i;
}
break;
case ol.filter.LogicalOperator.OR:
result = false;
while (!result && i < ii) {
result = result || filters[i].applies(feature);
++i;
}
break;
case ol.filter.LogicalOperator.NOT:
result = !filters[i].applies(feature);
break;
default:
goog.asserts.assert(false, 'Unsupported operation: ' + this.operator);
}
return !!result;
};
/**
* @return {Array.<ol.filter.Filter>} The filter's filters.
*/
ol.filter.Logical.prototype.getFilters = function() {
return this.filters_;
};
/**
* @enum {string}
*/
ol.filter.LogicalOperator = {
AND: '&&',
OR: '||',
NOT: '!'
};
/**
* Create a filter that evaluates to true if all of the provided filters
* evaluate to true.
* @param {...ol.filter.Filter} var_args Filters.
* @return {ol.filter.Logical} A logical AND filter.
*/
ol.filter.and = function(var_args) {
var filters = Array.prototype.slice.call(arguments);
return new ol.filter.Logical(filters, ol.filter.LogicalOperator.AND);
};
/**
* Create a new filter that is the logical compliment of another.
* @param {ol.filter.Filter} filter The filter to negate.
* @return {ol.filter.Logical} A logical NOT filter.
*/
ol.filter.not = function(filter) {
return new ol.filter.Logical([filter], ol.filter.LogicalOperator.NOT);
};
/**
* Create a filter that evaluates to true if any of the provided filters
* evaluate to true.
* @param {...ol.filter.Filter} var_args Filters.
* @return {ol.filter.Logical} A logical OR filter.
*/
ol.filter.or = function(var_args) {
var filters = Array.prototype.slice.call(arguments);
return new ol.filter.Logical(filters, ol.filter.LogicalOperator.OR);
};

View File

@@ -5,6 +5,11 @@ goog.require('goog.asserts');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('ol.Feature');
goog.require('ol.expr');
goog.require('ol.expr.Literal');
goog.require('ol.expr.Logical');
goog.require('ol.expr.LogicalOp');
goog.require('ol.expr.functions');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.SharedVertices');
goog.require('ol.layer.Layer');
@@ -79,35 +84,65 @@ ol.layer.FeatureCache.prototype.add = function(feature) {
/**
* @param {ol.filter.Filter=} opt_filter Optional filter.
* @param {ol.expr.Expression=} opt_expr Expression for filtering.
* @return {Object.<string, ol.Feature>} Object of features, keyed by id.
*/
ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_filter) {
var i, features;
if (!goog.isDef(opt_filter)) {
ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_expr) {
var features;
if (!goog.isDef(opt_expr)) {
features = this.idLookup_;
} else {
if (opt_filter instanceof ol.filter.Geometry) {
features = this.geometryTypeIndex_[opt_filter.getType()];
} else if (opt_filter instanceof ol.filter.Extent) {
features = this.rTree_.searchReturningObject(opt_filter.getExtent());
} else if (opt_filter instanceof ol.filter.Logical &&
opt_filter.operator === ol.filter.LogicalOperator.AND) {
var filters = opt_filter.getFilters();
if (filters.length === 2) {
var filter, geometryFilter, extentFilter;
for (i = 0; i <= 1; ++i) {
filter = filters[i];
if (filter instanceof ol.filter.Geometry) {
geometryFilter = filter;
} else if (filter instanceof ol.filter.Extent) {
extentFilter = filter;
// check for geometryType or extent expression
var name = ol.expr.isLibCall(opt_expr);
if (name === ol.expr.functions.GEOMETRY_TYPE) {
var args = /** @type {ol.expr.Call} */ (opt_expr).getArgs();
goog.asserts.assert(args.length === 1);
goog.asserts.assert(args[0] instanceof ol.expr.Literal);
var type = /** @type {ol.expr.Literal } */ (args[0]).evaluate();
goog.asserts.assertString(type);
features = this.geometryTypeIndex_[type];
} else if (name === ol.expr.functions.EXTENT) {
var args = /** @type {ol.expr.Call} */ (opt_expr).getArgs();
goog.asserts.assert(args.length === 4);
var extent = [];
for (var i = 0; i < 4; ++i) {
goog.asserts.assert(args[i] instanceof ol.expr.Literal);
extent[i] = /** @type {ol.expr.Literal} */ (args[i]).evaluate();
goog.asserts.assertNumber(extent[i]);
}
features = this.rTree_.searchReturningObject(extent);
} else {
// not a call expression, check logical
if (opt_expr instanceof ol.expr.Logical) {
var op = /** @type {ol.expr.Logical} */ (opt_expr).getOperator();
if (op === ol.expr.LogicalOp.AND) {
var expressions = [opt_expr.getLeft(), opt_expr.getRight()];
var expr, args, type, extent;
for (var i = 0; i <= 1; ++i) {
expr = expressions[i];
name = ol.expr.isLibCall(expr);
if (name === ol.expr.functions.GEOMETRY_TYPE) {
args = /** @type {ol.expr.Call} */ (expr).getArgs();
goog.asserts.assert(args.length === 1);
goog.asserts.assert(args[0] instanceof ol.expr.Literal);
type = /** @type {ol.expr.Literal } */ (args[0]).evaluate();
goog.asserts.assertString(type);
} else if (name === ol.expr.functions.EXTENT) {
args = /** @type {ol.expr.Call} */ (expr).getArgs();
goog.asserts.assert(args.length === 4);
extent = [];
for (var j = 0; j < 4; ++j) {
goog.asserts.assert(args[j] instanceof ol.expr.Literal);
extent[j] =
/** @type {ol.expr.Literal} */ (args[j]).evaluate();
goog.asserts.assertNumber(extent[j]);
}
}
}
if (type && extent) {
features = this.getFeaturesObjectForExtent(extent,
/** @type {ol.geom.GeometryType} */ (type));
}
}
if (extentFilter && geometryFilter) {
var type = geometryFilter.getType();
features = goog.object.isEmpty(this.geometryTypeIndex_[type]) ? {} :
this.rTree_.searchReturningObject(extentFilter.getExtent(), type);
}
}
}
@@ -118,7 +153,7 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_filter) {
features = {};
for (i in candidates) {
feature = candidates[i];
if (opt_filter.applies(feature) === true) {
if (ol.expr.evaluateFeature(opt_expr, feature)) {
features[i] = feature;
}
}
@@ -129,12 +164,22 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_filter) {
/**
* @param {ol.filter.Geometry} filter Geometry type filter.
* @return {Array.<ol.Feature>} Array of features.
* @private
* Get all features whose bounding box intersects the provided extent.
*
* @param {ol.Extent} extent Bounding extent.
* @param {ol.geom.GeometryType=} opt_type Optional geometry type.
* @return {Object.<string, ol.Feature>} Features.
*/
ol.layer.FeatureCache.prototype.getFeaturesByGeometryType_ = function(filter) {
return goog.object.getValues(this.geometryTypeIndex_[filter.getType()]);
ol.layer.FeatureCache.prototype.getFeaturesObjectForExtent = function(extent,
opt_type) {
var features;
if (goog.isDef(opt_type) &&
goog.object.isEmpty(this.geometryTypeIndex_[opt_type])) {
features = {};
} else {
features = this.rTree_.searchReturningObject(extent, opt_type);
}
return features;
};
@@ -234,21 +279,34 @@ ol.layer.Vector.prototype.getVectorSource = function() {
/**
* @param {ol.filter.Filter=} opt_filter Optional filter.
* @param {ol.expr.Expression=} opt_expr Expression for filtering.
* @return {Array.<ol.Feature>} Array of features.
*/
ol.layer.Vector.prototype.getFeatures = function(opt_filter) {
ol.layer.Vector.prototype.getFeatures = function(opt_expr) {
return goog.object.getValues(
this.featureCache_.getFeaturesObject(opt_filter));
this.featureCache_.getFeaturesObject(opt_expr));
};
/**
* @param {ol.filter.Filter=} opt_filter Optional filter.
* @param {ol.expr.Expression=} opt_expr Expression for filtering.
* @return {Object.<string, ol.Feature>} Features.
*/
ol.layer.Vector.prototype.getFeaturesObject = function(opt_filter) {
return this.featureCache_.getFeaturesObject(opt_filter);
ol.layer.Vector.prototype.getFeaturesObject = function(opt_expr) {
return this.featureCache_.getFeaturesObject(opt_expr);
};
/**
* Get all features whose bounding box intersects the provided extent.
*
* @param {ol.Extent} extent Bounding extent.
* @param {ol.geom.GeometryType=} opt_type Optional geometry type.
* @return {Object.<string, ol.Feature>} Features.
*/
ol.layer.Vector.prototype.getFeaturesObjectForExtent = function(extent,
opt_type) {
return this.featureCache_.getFeaturesObjectForExtent(extent, opt_type);
};
@@ -415,10 +473,3 @@ ol.layer.Vector.uidTransformFeatureInfo = function(features) {
function(feature) { return goog.getUid(feature); });
return featureIds.join(', ');
};
goog.require('ol.filter.Extent');
goog.require('ol.filter.Geometry');
goog.require('ol.filter.Logical');
goog.require('ol.filter.LogicalOperator');
goog.require('ol.geom.GeometryType');

View File

@@ -12,10 +12,6 @@ goog.require('ol.TileCoord');
goog.require('ol.TileRange');
goog.require('ol.ViewHint');
goog.require('ol.extent');
goog.require('ol.filter.Extent');
goog.require('ol.filter.Geometry');
goog.require('ol.filter.Logical');
goog.require('ol.filter.LogicalOperator');
goog.require('ol.geom.GeometryType');
goog.require('ol.layer.Vector');
goog.require('ol.renderer.canvas.Layer');
@@ -101,18 +97,18 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) {
this.tileArchetype_ = null;
/**
* Geometry filters in rendering order.
* Geometry types in rendering order.
* TODO: these will go away shortly (in favor of one call per symbolizer type)
* @private
* @type {Array.<ol.filter.Geometry>}
* @type {Array.<ol.geom.GeometryType>}
*/
this.geometryFilters_ = [
new ol.filter.Geometry(ol.geom.GeometryType.POINT),
new ol.filter.Geometry(ol.geom.GeometryType.MULTIPOINT),
new ol.filter.Geometry(ol.geom.GeometryType.LINESTRING),
new ol.filter.Geometry(ol.geom.GeometryType.MULTILINESTRING),
new ol.filter.Geometry(ol.geom.GeometryType.POLYGON),
new ol.filter.Geometry(ol.geom.GeometryType.MULTIPOLYGON)
this.geometryTypes_ = [
ol.geom.GeometryType.POINT,
ol.geom.GeometryType.MULTIPOINT,
ol.geom.GeometryType.LINESTRING,
ol.geom.GeometryType.MULTILINESTRING,
ol.geom.GeometryType.POLYGON,
ol.geom.GeometryType.MULTIPOLYGON
];
/**
@@ -251,13 +247,12 @@ ol.renderer.canvas.VectorLayer.prototype.getFeaturesForPixel =
var locationMin = [location[0] - halfMaxWidth, location[1] - halfMaxHeight];
var locationMax = [location[0] + halfMaxWidth, location[1] + halfMaxHeight];
var locationBbox = ol.extent.boundingExtent([locationMin, locationMax]);
var filter = new ol.filter.Extent(locationBbox);
var candidates = layer.getFeatures(filter);
var candidates = layer.getFeaturesObjectForExtent(locationBbox);
var candidate, geom, type, symbolBounds, symbolSize, halfWidth, halfHeight,
coordinates, j;
for (var i = 0, ii = candidates.length; i < ii; ++i) {
candidate = candidates[i];
for (var id in candidates) {
candidate = candidates[id];
geom = candidate.getGeometry();
type = geom.getType();
if (type === ol.geom.GeometryType.POINT ||
@@ -445,11 +440,11 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
var tileGutter = 15 * tileResolution;
var tile, tileCoord, key, x, y;
// render features by geometry type
var filters = this.geometryFilters_,
numFilters = filters.length,
var types = this.geometryTypes_,
numTypes = types.length,
deferred = false,
dirty = false,
i, geomFilter, tileExtent, extentFilter, type,
i, type, tileExtent,
groups, group, j, numGroups, featuresObject, tileHasFeatures;
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
@@ -463,16 +458,13 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
tileExtent[1] += tileGutter;
tileExtent[2] -= tileGutter;
tileExtent[3] += tileGutter;
extentFilter = new ol.filter.Extent(tileExtent);
tileHasFeatures = false;
for (i = 0; i < numFilters; ++i) {
geomFilter = filters[i];
type = geomFilter.getType();
for (i = 0; i < numTypes; ++i) {
type = types[i];
if (!goog.isDef(featuresToRender[type])) {
featuresToRender[type] = {};
}
featuresObject = layer.getFeaturesObject(new ol.filter.Logical(
[geomFilter, extentFilter], ol.filter.LogicalOperator.AND));
featuresObject = layer.getFeaturesObjectForExtent(tileExtent, type);
tileHasFeatures = tileHasFeatures ||
!goog.object.isEmpty(featuresObject);
goog.object.extend(featuresToRender[type], featuresObject);

View File

@@ -272,22 +272,12 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ =
ol.renderer.canvas.VectorRenderer.prototype.renderText_ =
function(features, text, texts) {
var context = this.context_,
fontArray = [],
color = text.color,
vecs, vec;
if (color) {
context.fillStyle = color;
}
if (goog.isDef(text.fontSize)) {
fontArray.push(text.fontSize + 'px');
}
if (goog.isDef(text.fontFamily)) {
fontArray.push(text.fontFamily);
}
if (fontArray.length) {
context.font = fontArray.join(' ');
if (context.fillStyle !== text.color) {
context.fillStyle = text.color;
}
context.font = text.fontSize + 'px ' + text.fontFamily;
context.globalAlpha = text.opacity;
// TODO: make alignments configurable

View File

@@ -3,8 +3,9 @@ goog.provide('ol.style.IconLiteral');
goog.provide('ol.style.IconType');
goog.require('goog.asserts');
goog.require('ol.Expression');
goog.require('ol.ExpressionLiteral');
goog.require('ol.expr');
goog.require('ol.expr.Expression');
goog.require('ol.expr.Literal');
goog.require('ol.style.Point');
goog.require('ol.style.PointLiteral');
@@ -69,47 +70,47 @@ ol.style.Icon = function(options) {
goog.asserts.assert(options.url, 'url must be set');
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.url_ = (options.url instanceof ol.Expression) ?
options.url : new ol.ExpressionLiteral(options.url);
this.url_ = (options.url instanceof ol.expr.Expression) ?
options.url : new ol.expr.Literal(options.url);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.width_ = !goog.isDef(options.width) ?
null :
(options.width instanceof ol.Expression) ?
options.width : new ol.ExpressionLiteral(options.width);
(options.width instanceof ol.expr.Expression) ?
options.width : new ol.expr.Literal(options.width);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.height_ = !goog.isDef(options.height) ?
null :
(options.height instanceof ol.Expression) ?
options.height : new ol.ExpressionLiteral(options.height);
(options.height instanceof ol.expr.Expression) ?
options.height : new ol.expr.Literal(options.height);
/**
* @type {ol.Expression}
* @type {ol.expr.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);
new ol.expr.Literal(ol.style.IconDefaults.opacity) :
(options.opacity instanceof ol.expr.Expression) ?
options.opacity : new ol.expr.Literal(options.opacity);
/**
* @type {ol.Expression}
* @type {ol.expr.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);
new ol.expr.Literal(ol.style.IconDefaults.rotation) :
(options.rotation instanceof ol.expr.Expression) ?
options.rotation : new ol.expr.Literal(options.rotation);
};
@@ -118,27 +119,28 @@ ol.style.Icon = function(options) {
* @inheritDoc
* @return {ol.style.IconLiteral} Literal shape symbolizer.
*/
ol.style.Icon.prototype.createLiteral = function(feature) {
var attrs = feature && feature.getAttributes();
ol.style.Icon.prototype.createLiteral = function(opt_feature) {
var url = /** @type {string} */ (this.url_.evaluate(feature, attrs));
goog.asserts.assert(goog.isString(url) && url != '#', 'url must be a string');
var url = ol.expr.evaluateFeature(this.url_, opt_feature);
goog.asserts.assertString(url, 'url must be a string');
goog.asserts.assert(url != '#', 'url must not be "#"');
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 width;
if (!goog.isNull(this.width_)) {
width = ol.expr.evaluateFeature(this.width_, opt_feature);
goog.asserts.assertNumber(width, 'width must be 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 height;
if (!goog.isNull(this.height_)) {
height = ol.expr.evaluateFeature(this.height_, opt_feature);
goog.asserts.assertNumber(height, 'height must be a number');
}
var opacity = /** {@type {number} */ (this.opacity_.evaluate(feature, attrs));
var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature);
goog.asserts.assertNumber(opacity, 'opacity must be a number');
var rotation =
/** {@type {number} */ (this.rotation_.evaluate(feature, attrs));
var rotation = ol.expr.evaluateFeature(this.rotation_, opt_feature);
goog.asserts.assertNumber(rotation, 'rotation must be a number');
return new ol.style.IconLiteral({

View File

@@ -2,8 +2,9 @@ goog.provide('ol.style.Line');
goog.provide('ol.style.LineLiteral');
goog.require('goog.asserts');
goog.require('ol.Expression');
goog.require('ol.ExpressionLiteral');
goog.require('ol.expr');
goog.require('ol.expr.Expression');
goog.require('ol.expr.Literal');
goog.require('ol.style.Symbolizer');
goog.require('ol.style.SymbolizerLiteral');
@@ -63,31 +64,31 @@ ol.style.Line = function(options) {
goog.base(this);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.strokeColor_ = !goog.isDef(options.strokeColor) ?
new ol.ExpressionLiteral(ol.style.LineDefaults.strokeColor) :
(options.strokeColor instanceof ol.Expression) ?
options.strokeColor : new ol.ExpressionLiteral(options.strokeColor);
new ol.expr.Literal(ol.style.LineDefaults.strokeColor) :
(options.strokeColor instanceof ol.expr.Expression) ?
options.strokeColor : new ol.expr.Literal(options.strokeColor);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.strokeWidth_ = !goog.isDef(options.strokeWidth) ?
new ol.ExpressionLiteral(ol.style.LineDefaults.strokeWidth) :
(options.strokeWidth instanceof ol.Expression) ?
options.strokeWidth : new ol.ExpressionLiteral(options.strokeWidth);
new ol.expr.Literal(ol.style.LineDefaults.strokeWidth) :
(options.strokeWidth instanceof ol.expr.Expression) ?
options.strokeWidth : new ol.expr.Literal(options.strokeWidth);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.opacity_ = !goog.isDef(options.opacity) ?
new ol.ExpressionLiteral(ol.style.LineDefaults.opacity) :
(options.opacity instanceof ol.Expression) ?
options.opacity : new ol.ExpressionLiteral(options.opacity);
new ol.expr.Literal(ol.style.LineDefaults.opacity) :
(options.opacity instanceof ol.expr.Expression) ?
options.opacity : new ol.expr.Literal(options.opacity);
};
goog.inherits(ol.style.Line, ol.style.Symbolizer);
@@ -98,19 +99,16 @@ goog.inherits(ol.style.Line, ol.style.Symbolizer);
* @return {ol.style.LineLiteral} Literal line symbolizer.
*/
ol.style.Line.prototype.createLiteral = function(opt_feature) {
var attrs,
feature = opt_feature;
if (goog.isDef(feature)) {
attrs = feature.getAttributes();
}
var strokeColor = this.strokeColor_.evaluate(feature, attrs);
var strokeColor = ol.expr.evaluateFeature(
this.strokeColor_, opt_feature);
goog.asserts.assertString(strokeColor, 'strokeColor must be a string');
var strokeWidth = this.strokeWidth_.evaluate(feature, attrs);
var strokeWidth = ol.expr.evaluateFeature(
this.strokeWidth_, opt_feature);
goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number');
var opacity = this.opacity_.evaluate(feature, attrs);
var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature);
goog.asserts.assertNumber(opacity, 'opacity must be a number');
return new ol.style.LineLiteral({

View File

@@ -2,8 +2,9 @@ goog.provide('ol.style.Polygon');
goog.provide('ol.style.PolygonLiteral');
goog.require('goog.asserts');
goog.require('ol.Expression');
goog.require('ol.ExpressionLiteral');
goog.require('ol.expr');
goog.require('ol.expr.Expression');
goog.require('ol.expr.Literal');
goog.require('ol.style.Symbolizer');
goog.require('ol.style.SymbolizerLiteral');
@@ -80,13 +81,13 @@ ol.style.Polygon = function(options) {
goog.base(this);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.fillColor_ = !goog.isDefAndNotNull(options.fillColor) ?
null :
(options.fillColor instanceof ol.Expression) ?
options.fillColor : new ol.ExpressionLiteral(options.fillColor);
(options.fillColor instanceof ol.expr.Expression) ?
options.fillColor : new ol.expr.Literal(options.fillColor);
// stroke handling - if any stroke property is supplied, use defaults
var strokeColor = null,
@@ -95,25 +96,33 @@ ol.style.Polygon = function(options) {
if (goog.isDefAndNotNull(options.strokeColor) ||
goog.isDefAndNotNull(options.strokeWidth)) {
strokeColor = !goog.isDefAndNotNull(options.strokeColor) ?
new ol.ExpressionLiteral(ol.style.PolygonDefaults.strokeColor) :
(options.strokeColor instanceof ol.Expression) ?
options.strokeColor : new ol.ExpressionLiteral(options.strokeColor);
if (goog.isDefAndNotNull(options.strokeColor)) {
strokeColor = (options.strokeColor instanceof ol.expr.Expression) ?
options.strokeColor :
new ol.expr.Literal(options.strokeColor);
} else {
strokeColor = new ol.expr.Literal(
/** @type {string} */ (ol.style.PolygonDefaults.strokeColor));
}
strokeWidth = !goog.isDef(options.strokeWidth) ?
new ol.ExpressionLiteral(ol.style.PolygonDefaults.strokeWidth) :
(options.strokeWidth instanceof ol.Expression) ?
options.strokeWidth : new ol.ExpressionLiteral(options.strokeWidth);
if (goog.isDefAndNotNull(options.strokeWidth)) {
strokeWidth = (options.strokeWidth instanceof ol.expr.Expression) ?
options.strokeWidth :
new ol.expr.Literal(options.strokeWidth);
} else {
strokeWidth = new ol.expr.Literal(
/** @type {number} */ (ol.style.PolygonDefaults.strokeWidth));
}
}
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.strokeColor_ = strokeColor;
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.strokeWidth_ = strokeWidth;
@@ -124,13 +133,13 @@ ol.style.Polygon = function(options) {
'Stroke or fill properties must be provided');
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.opacity_ = !goog.isDef(options.opacity) ?
new ol.ExpressionLiteral(ol.style.PolygonDefaults.opacity) :
(options.opacity instanceof ol.Expression) ?
options.opacity : new ol.ExpressionLiteral(options.opacity);
new ol.expr.Literal(ol.style.PolygonDefaults.opacity) :
(options.opacity instanceof ol.expr.Expression) ?
options.opacity : new ol.expr.Literal(options.opacity);
};
goog.inherits(ol.style.Polygon, ol.style.Symbolizer);
@@ -141,33 +150,31 @@ goog.inherits(ol.style.Polygon, ol.style.Symbolizer);
* @return {ol.style.PolygonLiteral} Literal shape symbolizer.
*/
ol.style.Polygon.prototype.createLiteral = function(opt_feature) {
var attrs,
feature = opt_feature;
if (goog.isDef(feature)) {
attrs = feature.getAttributes();
var fillColor;
if (!goog.isNull(this.fillColor_)) {
fillColor = ol.expr.evaluateFeature(this.fillColor_, opt_feature);
goog.asserts.assertString(fillColor, 'fillColor must be a string');
}
var fillColor = goog.isNull(this.fillColor_) ?
undefined :
/** @type {string} */ (this.fillColor_.evaluate(feature, attrs));
goog.asserts.assert(!goog.isDef(fillColor) || goog.isString(fillColor));
var strokeColor;
if (!goog.isNull(this.strokeColor_)) {
strokeColor = ol.expr.evaluateFeature(this.strokeColor_, opt_feature);
goog.asserts.assertString(strokeColor, 'strokeColor must be a string');
}
var strokeColor = goog.isNull(this.strokeColor_) ?
undefined :
/** @type {string} */ (this.strokeColor_.evaluate(feature, attrs));
goog.asserts.assert(!goog.isDef(strokeColor) || goog.isString(strokeColor));
var strokeWidth = goog.isNull(this.strokeWidth_) ?
undefined :
/** @type {number} */ (this.strokeWidth_.evaluate(feature, attrs));
goog.asserts.assert(!goog.isDef(strokeWidth) || goog.isNumber(strokeWidth));
var strokeWidth;
if (!goog.isNull(this.strokeWidth_)) {
strokeWidth = ol.expr.evaluateFeature(this.strokeWidth_, opt_feature);
goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number');
}
goog.asserts.assert(
goog.isDef(fillColor) ||
(goog.isDef(strokeColor) && goog.isDef(strokeWidth)),
'either fill style or strokeColor and strokeWidth must be defined');
'either fillColor or strokeColor and strokeWidth must be defined');
var opacity = this.opacity_.evaluate(feature, attrs);
var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature);
goog.asserts.assertNumber(opacity, 'opacity must be a number');
return new ol.style.PolygonLiteral({

View File

@@ -1,7 +1,10 @@
goog.provide('ol.style.Rule');
goog.require('goog.asserts');
goog.require('ol.Feature');
goog.require('ol.filter.Filter');
goog.require('ol.expr');
goog.require('ol.expr.Expression');
goog.require('ol.style.Symbolizer');
@@ -12,11 +15,22 @@ goog.require('ol.style.Symbolizer');
*/
ol.style.Rule = function(options) {
var filter = null;
if (goog.isDef(options.filter)) {
if (goog.isString(options.filter)) {
filter = ol.expr.parse(options.filter);
} else {
goog.asserts.assert(options.filter instanceof ol.expr.Expression);
filter = options.filter;
}
}
/**
* @type {ol.filter.Filter}
* @type {ol.expr.Expression}
* @private
*/
this.filter_ = goog.isDef(options.filter) ? options.filter : null;
this.filter_ = filter;
/**
* @type {Array.<ol.style.Symbolizer>}
@@ -33,7 +47,8 @@ ol.style.Rule = function(options) {
* @return {boolean} Does the rule apply to the feature?
*/
ol.style.Rule.prototype.applies = function(feature) {
return goog.isNull(this.filter_) ? true : this.filter_.applies(feature);
return goog.isNull(this.filter_) ?
true : !!ol.expr.evaluateFeature(this.filter_, feature);
};

View File

@@ -3,8 +3,9 @@ goog.provide('ol.style.ShapeLiteral');
goog.provide('ol.style.ShapeType');
goog.require('goog.asserts');
goog.require('ol.Expression');
goog.require('ol.ExpressionLiteral');
goog.require('ol.expr');
goog.require('ol.expr.Expression');
goog.require('ol.expr.Literal');
goog.require('ol.style.Point');
goog.require('ol.style.PointLiteral');
@@ -106,22 +107,22 @@ ol.style.Shape = function(options) {
options.type : ol.style.ShapeDefaults.type);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.size_ = !goog.isDef(options.size) ?
new ol.ExpressionLiteral(ol.style.ShapeDefaults.size) :
(options.size instanceof ol.Expression) ?
options.size : new ol.ExpressionLiteral(options.size);
new ol.expr.Literal(ol.style.ShapeDefaults.size) :
(options.size instanceof ol.expr.Expression) ?
options.size : new ol.expr.Literal(options.size);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.fillColor_ = !goog.isDefAndNotNull(options.fillColor) ?
null :
(options.fillColor instanceof ol.Expression) ?
options.fillColor : new ol.ExpressionLiteral(options.fillColor);
(options.fillColor instanceof ol.expr.Expression) ?
options.fillColor : new ol.expr.Literal(options.fillColor);
// stroke handling - if any stroke property is supplied, use defaults
var strokeColor = null,
@@ -130,25 +131,34 @@ ol.style.Shape = function(options) {
if (goog.isDefAndNotNull(options.strokeColor) ||
goog.isDefAndNotNull(options.strokeWidth)) {
strokeColor = !goog.isDefAndNotNull(options.strokeColor) ?
new ol.ExpressionLiteral(ol.style.ShapeDefaults.strokeColor) :
(options.strokeColor instanceof ol.Expression) ?
options.strokeColor : new ol.ExpressionLiteral(options.strokeColor);
if (goog.isDefAndNotNull(options.strokeColor)) {
strokeColor = (options.strokeColor instanceof ol.expr.Expression) ?
options.strokeColor :
new ol.expr.Literal(options.strokeColor);
} else {
strokeColor = new ol.expr.Literal(
/** @type {string} */ (ol.style.ShapeDefaults.strokeColor));
}
if (goog.isDefAndNotNull(options.strokeWidth)) {
strokeWidth = (options.strokeWidth instanceof ol.expr.Expression) ?
options.strokeWidth :
new ol.expr.Literal(options.strokeWidth);
} else {
strokeWidth = new ol.expr.Literal(
/** @type {number} */ (ol.style.ShapeDefaults.strokeWidth));
}
strokeWidth = !goog.isDef(options.strokeWidth) ?
new ol.ExpressionLiteral(ol.style.ShapeDefaults.strokeWidth) :
(options.strokeWidth instanceof ol.Expression) ?
options.strokeWidth : new ol.ExpressionLiteral(options.strokeWidth);
}
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.strokeColor_ = strokeColor;
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.strokeWidth_ = strokeWidth;
@@ -159,13 +169,13 @@ ol.style.Shape = function(options) {
'Stroke or fill properties must be provided');
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.opacity_ = !goog.isDef(options.opacity) ?
new ol.ExpressionLiteral(ol.style.ShapeDefaults.opacity) :
(options.opacity instanceof ol.Expression) ?
options.opacity : new ol.ExpressionLiteral(options.opacity);
new ol.expr.Literal(ol.style.ShapeDefaults.opacity) :
(options.opacity instanceof ol.expr.Expression) ?
options.opacity : new ol.expr.Literal(options.opacity);
};
@@ -175,36 +185,34 @@ ol.style.Shape = function(options) {
* @return {ol.style.ShapeLiteral} Literal shape symbolizer.
*/
ol.style.Shape.prototype.createLiteral = function(opt_feature) {
var attrs,
feature = opt_feature;
if (goog.isDef(feature)) {
attrs = feature.getAttributes();
}
var size = this.size_.evaluate(feature, attrs);
var size = ol.expr.evaluateFeature(this.size_, opt_feature);
goog.asserts.assertNumber(size, 'size must be a number');
var fillColor = goog.isNull(this.fillColor_) ?
undefined :
/** @type {string} */ (this.fillColor_.evaluate(feature, attrs));
goog.asserts.assert(!goog.isDef(fillColor) || goog.isString(fillColor));
var fillColor;
if (!goog.isNull(this.fillColor_)) {
fillColor = ol.expr.evaluateFeature(this.fillColor_, opt_feature);
goog.asserts.assertString(fillColor, 'fillColor must be a string');
}
var strokeColor = goog.isNull(this.strokeColor_) ?
undefined :
/** @type {string} */ (this.strokeColor_.evaluate(feature, attrs));
goog.asserts.assert(!goog.isDef(strokeColor) || goog.isString(strokeColor));
var strokeColor;
if (!goog.isNull(this.strokeColor_)) {
strokeColor = ol.expr.evaluateFeature(this.strokeColor_, opt_feature);
goog.asserts.assertString(strokeColor, 'strokeColor must be a string');
}
var strokeWidth = goog.isNull(this.strokeWidth_) ?
undefined :
/** @type {number} */ (this.strokeWidth_.evaluate(feature, attrs));
goog.asserts.assert(!goog.isDef(strokeWidth) || goog.isNumber(strokeWidth));
var strokeWidth;
if (!goog.isNull(this.strokeWidth_)) {
strokeWidth = ol.expr.evaluateFeature(this.strokeWidth_, opt_feature);
goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number');
}
goog.asserts.assert(
goog.isDef(fillColor) ||
(goog.isDef(strokeColor) && goog.isDef(strokeWidth)),
'either fill style or strokeColor and strokeWidth must be defined');
'either fillColor or strokeColor and strokeWidth must be defined');
var opacity = this.opacity_.evaluate(feature, attrs);
var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature);
goog.asserts.assertNumber(opacity, 'opacity must be a number');
return new ol.style.ShapeLiteral({

View File

@@ -2,8 +2,9 @@ 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.expr');
goog.require('ol.expr.Expression');
goog.require('ol.expr.Literal');
goog.require('ol.style.Symbolizer');
goog.require('ol.style.SymbolizerLiteral');
@@ -26,18 +27,13 @@ ol.style.TextLiteralOptions;
*/
ol.style.TextLiteral = function(options) {
goog.asserts.assertString(options.color, 'color must be a string');
/** @type {string} */
this.color = options.color;
if (goog.isDef(options.color)) {
goog.asserts.assertString(options.color, 'color must be a string');
}
goog.asserts.assertString(options.fontFamily, 'fontFamily must be a string');
/** @type {string} */
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} */
@@ -75,47 +71,47 @@ ol.style.TextLiteral.prototype.equals = function(textLiteral) {
ol.style.Text = function(options) {
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.color_ = !goog.isDef(options.color) ?
new ol.ExpressionLiteral(ol.style.TextDefaults.color) :
(options.color instanceof ol.Expression) ?
options.color : new ol.ExpressionLiteral(options.color);
new ol.expr.Literal(ol.style.TextDefaults.color) :
(options.color instanceof ol.expr.Expression) ?
options.color : new ol.expr.Literal(options.color);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.fontFamily_ = !goog.isDef(options.fontFamily) ?
new ol.ExpressionLiteral(ol.style.TextDefaults.fontFamily) :
(options.fontFamily instanceof ol.Expression) ?
options.fontFamily : new ol.ExpressionLiteral(options.fontFamily);
new ol.expr.Literal(ol.style.TextDefaults.fontFamily) :
(options.fontFamily instanceof ol.expr.Expression) ?
options.fontFamily : new ol.expr.Literal(options.fontFamily);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.fontSize_ = !goog.isDef(options.fontSize) ?
new ol.ExpressionLiteral(ol.style.TextDefaults.fontSize) :
(options.fontSize instanceof ol.Expression) ?
options.fontSize : new ol.ExpressionLiteral(options.fontSize);
new ol.expr.Literal(ol.style.TextDefaults.fontSize) :
(options.fontSize instanceof ol.expr.Expression) ?
options.fontSize : new ol.expr.Literal(options.fontSize);
/**
* @type {ol.Expression}
* @type {ol.expr.Expression}
* @private
*/
this.text_ = (options.text instanceof ol.Expression) ?
options.text : new ol.ExpressionLiteral(options.text);
this.text_ = (options.text instanceof ol.expr.Expression) ?
options.text : new ol.expr.Literal(options.text);
/**
* @type {ol.Expression}
* @type {ol.expr.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);
new ol.expr.Literal(ol.style.TextDefaults.opacity) :
(options.opacity instanceof ol.expr.Expression) ?
options.opacity : new ol.expr.Literal(options.opacity);
};
goog.inherits(ol.style.Text, ol.style.Symbolizer);
@@ -126,25 +122,20 @@ goog.inherits(ol.style.Text, ol.style.Symbolizer);
* @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 = this.color_.evaluate(feature, attrs);
var color = ol.expr.evaluateFeature(this.color_, opt_feature);
goog.asserts.assertString(color, 'color must be a string');
var fontFamily = this.fontFamily_.evaluate(feature, attrs);
var fontFamily = ol.expr.evaluateFeature(this.fontFamily_, opt_feature);
goog.asserts.assertString(fontFamily, 'fontFamily must be a string');
var fontSize = this.fontSize_.evaluate(feature, attrs);
var fontSize = ol.expr.evaluateFeature(this.fontSize_, opt_feature);
goog.asserts.assertNumber(fontSize, 'fontSize must be a number');
var text = this.text_.evaluate(feature, attrs);
var text = ol.expr.evaluateFeature(this.text_, opt_feature);
goog.asserts.assertString(text, 'text must be a string');
var opacity = this.opacity_.evaluate(feature, attrs);
var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature);
goog.asserts.assertNumber(opacity, 'opacity must be a number');
return new ol.style.TextLiteral({