Add parser for OGC Filter 1.0 and 1.1 (read/write)

This work is based on the ol.expr package by @tschaub.
It adds a few named expression functions to that package:

 * INTERSECTS, CONTAINS, DWITHIN, WITHIN (no client-side implementation as yet)
 * LIKE (like comparison with wildcard, singleChar and escapeChar)
 * IEQ (case-insensitive equality)
 * INEQ (case-insensitive non-equality)

It also adds a few extra parameters to the existing EXTENT function to be able
to deserialize and serialize all info (i.e. projection and property name).

Some changes were needed for the GML parser as well:

 * createGeometry function needed to be public
 * when parsing Box (GML2) and Envelope (GML3) also parse the srsName
 * fix up writing for Box and Envelope now that bounds is an array

Also added createDocumentFragment function to the XML parser. Implementation
is similar to OpenLayers 2.

Some addtional notes on the implementation:

 * PropertyIsBetween was implemented as an ol.expr.Logical with operator
   ol.expr.LogicalOp.AND and two ol.expr.Comparison instances with operator
   ol.expr.ComparisonOp.GTE and ol.expr.ComparisonOp.LTE
 * In OGC Filter And and Or can contain more than 2 sub filters, so this
   is translated into a hierarchy of ol.expr.Logical
This commit is contained in:
Bart van den Eijnden
2013-07-01 13:46:41 +02:00
parent a7ca22dde0
commit ab40ab6208
37 changed files with 1899 additions and 39 deletions

View File

@@ -97,7 +97,14 @@ ol.expr.lib = {};
ol.expr.functions = {
EXTENT: 'extent',
FID: 'fid',
GEOMETRY_TYPE: 'geometryType'
GEOMETRY_TYPE: 'geometryType',
INTERSECTS: 'intersects',
CONTAINS: 'contains',
DWITHIN: 'dwithin',
WITHIN: 'within',
LIKE: 'like',
IEQ: 'ieq',
INEQ: 'ineq'
};
@@ -107,12 +114,16 @@ ol.expr.functions = {
* @param {number} maxX Maximum x-coordinate value.
* @param {number} minY Minimum y-coordinate value.
* @param {number} maxY Maximum y-coordinate value.
* @param {string=} opt_projection Projection of the extent.
* @param {string=} opt_attribute Name of the geometry attribute to use.
* @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) {
ol.expr.lib[ol.expr.functions.EXTENT] = function(minX, maxX, minY, maxY,
opt_projection, opt_attribute) {
var intersects = false;
var geometry = this.getGeometry();
var geometry = goog.isDef(opt_attribute) ?
this.getAttributes()[opt_attribute] : this.getGeometry();
if (geometry) {
intersects = ol.extent.intersects(geometry.getBounds(),
[minX, maxX, minY, maxY]);
@@ -142,6 +153,75 @@ ol.expr.lib[ol.expr.functions.FID] = function(var_args) {
};
/**
* Determine if a feature attribute is like the provided value.
* @param {string} attribute The name of the attribute to test for.
* @param {string} value The value to test for.
* @param {string} wildCard The wildcard character to use.
* @param {string} singleChar The single character to use.
* @param {string} escapeChar The escape character to use.
* @param {boolean} matchCase Should we match case or not?
* @this {ol.Feature}
*/
ol.expr.lib[ol.expr.functions.LIKE] = function(attribute, value, wildCard,
singleChar, escapeChar, matchCase) {
if (wildCard == '.') {
throw new Error('"." is an unsupported wildCard character for ' +
'ol.filter.Comparison');
}
// set UMN MapServer defaults for unspecified parameters
wildCard = goog.isDef(wildCard) ? wildCard : '*';
singleChar = goog.isDef(singleChar) ? singleChar : '.';
escapeChar = goog.isDef(escapeChar) ? escapeChar : '!';
var val;
val = value.replace(
new RegExp('\\' + escapeChar + '(.|$)', 'g'), '\\$1');
val = value.replace(
new RegExp('\\' + singleChar, 'g'), '.');
val = value.replace(
new RegExp('\\' + wildCard, 'g'), '.*');
val = value.replace(
new RegExp('\\\\.\\*', 'g'), '\\' + wildCard);
val = value.replace(
new RegExp('\\\\\\.', 'g'), '\\' + singleChar);
var attributes = this.getAttributes();
var modifiers = (matchCase === false) ? 'gi' : 'g';
return new RegExp(val, modifiers).test(attributes[attribute]);
};
/**
* Case insensitive comparison for equality.
* @param {string} attribute Name of the attribute.
* @param {string} value Value to test for equality.
* @this {ol.Feature}
*/
ol.expr.lib[ol.expr.functions.IEQ] = function(attribute, value) {
var attributes = this.getAttributes();
if (goog.isString(value) && goog.isString(attributes[attribute])) {
return value.toUpperCase() == attributes[attribute].toUpperCase();
} else {
return value == attributes[attribute];
}
};
/**
* Case insensitive comparison for non-equality.
* @param {string} attribute Name of the attribute.
* @param {string} value Value to test for non-equality.
* @this {ol.Feature}
*/
ol.expr.lib[ol.expr.functions.INEQ] = function(attribute, value) {
var attributes = this.getAttributes();
if (goog.isString(value) && goog.isString(attributes[attribute])) {
return value.toUpperCase() == attributes[attribute].toUpperCase();
} else {
return value != attributes[attribute];
}
};
/**
* Determine if a feature's default geometry is of the given type.
* @param {ol.geom.GeometryType} type Geometry type.
@@ -156,3 +236,31 @@ ol.expr.lib[ol.expr.functions.GEOMETRY_TYPE] = function(type) {
}
return same;
};
ol.expr.lib[ol.expr.functions.INTERSECTS] = function(geom, opt_projection,
opt_attribute) {
throw new Error('Spatial function not implemented: ' +
ol.expr.functions.INTERSECTS);
};
ol.expr.lib[ol.expr.functions.WITHIN] = function(geom, opt_projection,
opt_attribute) {
throw new Error('Spatial function not implemented: ' +
ol.expr.functions.WITHIN);
};
ol.expr.lib[ol.expr.functions.CONTAINS] = function(geom, opt_projeciton,
opt_attribute) {
throw new Error('Spatial function not implemented: ' +
ol.expr.functions.CONTAINS);
};
ol.expr.lib[ol.expr.functions.DWITHIN] = function(geom, distance, units,
opt_projection, opt_attribute) {
throw new Error('Spatial function not implemented: ' +
ol.expr.functions.DWITHIN);
};

View File

@@ -0,0 +1,37 @@
goog.provide('ol.parser.ogc.Filter');
goog.require('ol.parser.ogc.Filter_v1_0_0');
goog.require('ol.parser.ogc.Filter_v1_1_0');
goog.require('ol.parser.ogc.Versioned');
/**
* @define {boolean} Whether to enable OGC Filter version 1.0.0.
*/
ol.ENABLE_OGCFILTER_1_0_0 = true;
/**
* @define {boolean} Whether to enable OGC Filter version 1.1.0.
*/
ol.ENABLE_OGCFILTER_1_1_0 = true;
/**
* @constructor
* @param {Object=} opt_options Options which will be set on this object.
* @extends {ol.parser.ogc.Versioned}
*/
ol.parser.ogc.Filter = function(opt_options) {
opt_options = opt_options || {};
opt_options['defaultVersion'] = '1.0.0';
this.parsers = {};
if (ol.ENABLE_OGCFILTER_1_0_0) {
this.parsers['v1_0_0'] = ol.parser.ogc.Filter_v1_0_0;
}
if (ol.ENABLE_OGCFILTER_1_1_0) {
this.parsers['v1_1_0'] = ol.parser.ogc.Filter_v1_1_0;
}
goog.base(this, opt_options);
};
goog.inherits(ol.parser.ogc.Filter, ol.parser.ogc.Versioned);

View File

@@ -0,0 +1,582 @@
goog.provide('ol.parser.ogc.Filter_v1');
goog.require('goog.array');
goog.require('goog.dom.xml');
goog.require('goog.string');
goog.require('ol.expr');
goog.require('ol.expr.Call');
goog.require('ol.expr.Comparison');
goog.require('ol.expr.ComparisonOp');
goog.require('ol.expr.Identifier');
goog.require('ol.expr.Logical');
goog.require('ol.expr.LogicalOp');
goog.require('ol.expr.Not');
goog.require('ol.expr.functions');
goog.require('ol.parser.XML');
/**
* @constructor
* @extends {ol.parser.XML}
*/
ol.parser.ogc.Filter_v1 = function() {
this.defaultNamespaceURI = 'http://www.opengis.net/ogc';
this.errorProperty = 'filter';
this.readers = {
'http://www.opengis.net/ogc': {
'_expression': function(node) {
// only the simplest of ogc:expression handled
// "some text and an <PropertyName>attribute</PropertyName>"
var obj, value = '';
for (var child = node.firstChild; child; child = child.nextSibling) {
switch (child.nodeType) {
case 1:
obj = this.readNode(child);
if (obj['property']) {
value += obj['property'];
} else if (goog.isDef(obj['value'])) {
value += obj['value'];
}
break;
case 3: // text node
case 4: // cdata section
value += child.nodeValue;
break;
default:
break;
}
}
return value;
},
'Filter': function(node, obj) {
var container = {
'filters': []
};
this.readChildNodes(node, container);
if (goog.isDef(container['fids'])) {
obj['filter'] = new ol.expr.Call(
new ol.expr.Identifier(ol.expr.functions.FID),
container['fids']);
} else if (container['filters'].length > 0) {
obj['filter'] = container['filters'][0];
}
},
'FeatureId': function(node, obj) {
var fid = node.getAttribute('fid');
if (fid) {
if (!goog.isDef(obj['fids'])) {
obj['fids'] = {};
}
obj['fids'][fid] = true;
}
},
'And': function(node, obj) {
var container = {'filters': []};
this.readChildNodes(node, container);
var filter = this.aggregateLogical_(container['filters'],
ol.expr.LogicalOp.AND);
obj['filters'].push(filter);
},
'Or': function(node, obj) {
var container = {'filters': []};
this.readChildNodes(node, container);
var filter = this.aggregateLogical_(container['filters'],
ol.expr.LogicalOp.OR);
obj['filters'].push(filter);
},
'Not': function(node, obj) {
var container = {'filters': []};
this.readChildNodes(node, container);
// Not is unary so can only contain 1 child filter
obj['filters'].push(new ol.expr.Not(
container.filters[0]));
},
'PropertyIsNull': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
obj['filters'].push(new ol.expr.Comparison(
ol.expr.ComparisonOp.EQ,
container['property'],
null));
},
'PropertyIsLessThan': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
obj['filters'].push(new ol.expr.Comparison(
ol.expr.ComparisonOp.LT,
container['property'],
container['value']));
},
'PropertyIsGreaterThan': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
obj['filters'].push(new ol.expr.Comparison(
ol.expr.ComparisonOp.GT,
container['property'],
container['value']));
},
'PropertyIsLessThanOrEqualTo': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
obj['filters'].push(new ol.expr.Comparison(
ol.expr.ComparisonOp.LTE,
container['property'],
container['value']));
},
'PropertyIsGreaterThanOrEqualTo': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
obj['filters'].push(new ol.expr.Comparison(
ol.expr.ComparisonOp.GTE,
container['property'],
container['value']));
},
'PropertyIsBetween': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
obj['filters'].push(new ol.expr.Logical(ol.expr.LogicalOp.AND,
new ol.expr.Comparison(ol.expr.ComparisonOp.GTE,
container['property'], container['lowerBoundary']),
new ol.expr.Comparison(ol.expr.ComparisonOp.LTE,
container['property'], container['upperBoundary'])));
},
'Literal': function(node, obj) {
var nodeValue = this.getChildValue(node);
var value = goog.string.toNumber(nodeValue);
obj['value'] = isNaN(value) ? nodeValue : value;
},
'PropertyName': function(node, obj) {
obj['property'] = this.getChildValue(node);
},
'LowerBoundary': function(node, obj) {
var readers = this.readers[this.defaultNamespaceURI];
obj['lowerBoundary'] = goog.string.toNumber(
readers['_expression'].call(this, node));
},
'UpperBoundary': function(node, obj) {
var readers = this.readers[this.defaultNamespaceURI];
obj['upperBoundary'] = goog.string.toNumber(
readers['_expression'].call(this, node));
},
'_spatial': function(node, obj, identifier) {
var args = [], container = {};
this.readChildNodes(node, container);
if (goog.isDef(container.geometry)) {
args.push(this.gml_.createGeometry(container));
} else {
args = container['bounds'];
}
if (goog.isDef(container['distance'])) {
args.push(container['distance']);
}
if (goog.isDef(container['distanceUnits'])) {
args.push(container['distanceUnits']);
}
args.push(container['projection']);
if (goog.isDef(container['property'])) {
args.push(container['property']);
}
obj['filters'].push(new ol.expr.Call(new ol.expr.Identifier(
identifier), args));
},
'BBOX': function(node, obj) {
var readers = this.readers[this.defaultNamespaceURI];
readers['_spatial'].call(this, node, obj,
ol.expr.functions.EXTENT);
},
'Intersects': function(node, obj) {
var readers = this.readers[this.defaultNamespaceURI];
readers['_spatial'].call(this, node, obj,
ol.expr.functions.INTERSECTS);
},
'Within': function(node, obj) {
var readers = this.readers[this.defaultNamespaceURI];
readers['_spatial'].call(this, node, obj,
ol.expr.functions.WITHIN);
},
'Contains': function(node, obj) {
var readers = this.readers[this.defaultNamespaceURI];
readers['_spatial'].call(this, node, obj,
ol.expr.functions.CONTAINS);
},
'DWithin': function(node, obj) {
var readers = this.readers[this.defaultNamespaceURI];
readers['_spatial'].call(this, node, obj,
ol.expr.functions.DWITHIN);
},
'Distance': function(node, obj) {
obj['distance'] = parseInt(this.getChildValue(node), 10);
obj['distanceUnits'] = node.getAttribute('units');
}
}
};
this.writers = {
'http://www.opengis.net/ogc': {
'Filter': function(filter) {
var node = this.createElementNS('ogc:Filter');
this.writeNode(this.getFilterType_(filter), filter, null, node);
return node;
},
'_featureIds': function(filter) {
var node = this.createDocumentFragment();
var fids = filter.getArgs();
for (var i = 0, ii = fids.length; i < ii; i++) {
this.writeNode('FeatureId', fids[i], null, node);
}
return node;
},
'FeatureId': function(fid) {
var node = this.createElementNS('ogc:FeatureId');
node.setAttribute('fid', fid);
return node;
},
'And': function(filter) {
var node = this.createElementNS('ogc:And');
var subFilters = [];
this.getSubfiltersForLogical_(filter, subFilters);
for (var i = 0, ii = subFilters.length; i < ii; ++i) {
var subFilter = subFilters[i];
if (goog.isDefAndNotNull(subFilter)) {
this.writeNode(this.getFilterType_(subFilter), subFilter,
null, node);
}
}
return node;
},
'Or': function(filter) {
var node = this.createElementNS('ogc:Or');
var subFilters = [];
this.getSubfiltersForLogical_(filter, subFilters);
for (var i = 0, ii = subFilters.length; i < ii; ++i) {
var subFilter = subFilters[i];
if (goog.isDefAndNotNull(subFilter)) {
this.writeNode(this.getFilterType_(subFilter), subFilter,
null, node);
}
}
return node;
},
'Not': function(filter) {
var node = this.createElementNS('ogc:Not');
var childFilter = filter.getArgument();
this.writeNode(this.getFilterType_(childFilter), childFilter, null,
node);
return node;
},
'PropertyIsLessThan': function(filter) {
var node = this.createElementNS('ogc:PropertyIsLessThan');
this.writeNode('PropertyName', filter.getLeft(), null, node);
this.writeOgcExpression(filter.getRight(), node);
return node;
},
'PropertyIsGreaterThan': function(filter) {
var node = this.createElementNS('ogc:PropertyIsGreaterThan');
this.writeNode('PropertyName', filter.getLeft(), null, node);
this.writeOgcExpression(filter.getRight(), node);
return node;
},
'PropertyIsLessThanOrEqualTo': function(filter) {
var node = this.createElementNS('ogc:PropertyIsLessThanOrEqualTo');
this.writeNode('PropertyName', filter.getLeft(), null, node);
this.writeOgcExpression(filter.getRight(), node);
return node;
},
'PropertyIsGreaterThanOrEqualTo': function(filter) {
var node = this.createElementNS('ogc:PropertyIsGreaterThanOrEqualTo');
this.writeNode('PropertyName', filter.getLeft(), null, node);
this.writeOgcExpression(filter.getRight(), node);
return node;
},
'PropertyIsBetween': function(filter) {
var node = this.createElementNS('ogc:PropertyIsBetween');
var property = filter.getLeft().getLeft();
this.writeNode('PropertyName', property, null, node);
var lower, upper;
var filters = new Array(2);
filters[0] = filter.getLeft();
filters[1] = filter.getRight();
for (var i = 0; i < 2; ++i) {
var value = filters[i].getRight();
if (filters[i].getOperator() === ol.expr.ComparisonOp.GTE) {
lower = value;
} else if (filters[i].getOperator() === ol.expr.ComparisonOp.LTE) {
upper = value;
}
}
this.writeNode('LowerBoundary', lower, null, node);
this.writeNode('UpperBoundary', upper, null, node);
return node;
},
'PropertyName': function(name) {
var node = this.createElementNS('ogc:PropertyName');
node.appendChild(this.createTextNode(name));
return node;
},
'Literal': function(value) {
if (value instanceof Date) {
value = value.toISOString();
}
var node = this.createElementNS('ogc:Literal');
node.appendChild(this.createTextNode(value));
return node;
},
'LowerBoundary': function(value) {
var node = this.createElementNS('ogc:LowerBoundary');
this.writeOgcExpression(value, node);
return node;
},
'UpperBoundary': function(value) {
var node = this.createElementNS('ogc:UpperBoundary');
this.writeOgcExpression(value, node);
return node;
},
'INTERSECTS': function(filter) {
return this.writeSpatial_(filter, 'Intersects');
},
'WITHIN': function(filter) {
return this.writeSpatial_(filter, 'Within');
},
'CONTAINS': function(filter) {
return this.writeSpatial_(filter, 'Contains');
},
'DWITHIN': function(filter) {
var node = this.writeSpatial_(filter, 'DWithin');
this.writeNode('Distance', filter, null, node);
return node;
},
'Distance': function(filter) {
var node = this.createElementNS('ogc:Distance');
var args = filter.getArgs();
node.setAttribute('units', args[2]);
node.appendChild(this.createTextNode(args[1]));
return node;
},
'Function': function(filter) {
var node = this.createElementNS('ogc:Function');
node.setAttribute('name', filter.getCallee().getName());
var params = filter.getArgs();
for (var i = 0, len = params.length; i < len; i++) {
this.writeOgcExpression(params[i], node);
}
return node;
},
'PropertyIsNull': function(filter) {
var node = this.createElementNS('ogc:PropertyIsNull');
this.writeNode('PropertyName', filter.getLeft(), null, node);
return node;
}
}
};
this.filterMap_ = {
'&&': 'And',
'||': 'Or',
'!': 'Not',
'==': 'PropertyIsEqualTo',
'!=': 'PropertyIsNotEqualTo',
'<': 'PropertyIsLessThan',
'>': 'PropertyIsGreaterThan',
'<=': 'PropertyIsLessThanOrEqualTo',
'>=': 'PropertyIsGreaterThanOrEqualTo',
'..': 'PropertyIsBetween',
'~': 'PropertyIsLike',
'NULL': 'PropertyIsNull',
'BBOX': 'BBOX',
'DWITHIN': 'DWITHIN',
'WITHIN': 'WITHIN',
'CONTAINS': 'CONTAINS',
'INTERSECTS': 'INTERSECTS',
'FID': '_featureIds'
};
goog.base(this);
};
goog.inherits(ol.parser.ogc.Filter_v1, ol.parser.XML);
/**
* @param {ol.expr.Expression} filter The filter to determine the type of.
* @return {string} The type of filter.
* @private
*/
ol.parser.ogc.Filter_v1.prototype.getFilterType_ = function(filter) {
var type;
if (filter instanceof ol.expr.Logical ||
filter instanceof ol.expr.Comparison) {
type = filter.getOperator();
var isNull = (type === ol.expr.ComparisonOp.EQ &&
filter.getRight() === null);
if (isNull) {
type = 'NULL';
}
var isBetween = (type === ol.expr.LogicalOp.AND &&
filter.getLeft() instanceof ol.expr.Comparison &&
filter.getRight() instanceof ol.expr.Comparison &&
filter.getLeft().getLeft() === filter.getRight().getLeft() &&
(filter.getLeft().getOperator() === ol.expr.ComparisonOp.LTE ||
filter.getLeft().getOperator() === ol.expr.ComparisonOp.GTE) &&
(filter.getRight().getOperator() === ol.expr.ComparisonOp.LTE ||
filter.getRight().getOperator() === ol.expr.ComparisonOp.GTE));
if (isBetween) {
type = '..';
}
} else if (filter instanceof ol.expr.Call) {
var callee = filter.getCallee().getName();
if (callee === ol.expr.functions.FID) {
type = 'FID';
}
else if (callee === ol.expr.functions.IEQ) {
type = '==';
} else if (callee === ol.expr.functions.INEQ) {
type = '!=';
}
else if (callee === ol.expr.functions.LIKE) {
type = '~';
}
else if (callee === ol.expr.functions.CONTAINS) {
type = 'CONTAINS';
} else if (callee === ol.expr.functions.WITHIN) {
type = 'WITHIN';
} else if (callee === ol.expr.functions.DWITHIN) {
type = 'DWITHIN';
} else if (callee === ol.expr.functions.EXTENT) {
type = 'BBOX';
} else if (callee === ol.expr.functions.INTERSECTS) {
type = 'INTERSECTS';
}
} else if (filter instanceof ol.expr.Not) {
type = '!';
}
var filterType = this.filterMap_[type];
if (!filterType) {
throw new Error('Filter writing not supported for rule type: ' + type);
}
return filterType;
};
/**
* @param {string|Document|Element} data Data to read.
* @return {Object} An object representing the document.
*/
ol.parser.ogc.Filter_v1.prototype.read = function(data) {
if (goog.isString(data)) {
data = goog.dom.xml.loadXml(data);
}
if (data && data.nodeType == 9) {
data = data.documentElement;
}
var obj = {};
this.readNode(data, obj);
return obj['filter'];
};
/**
* @param {ol.expr.Expression} filter The filter to write out.
* @return {string} The serialized filter.
*/
ol.parser.ogc.Filter_v1.prototype.write = function(filter) {
var root = this.writeNode('Filter', filter);
this.setAttributeNS(
root, 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation', this.schemaLocation);
return this.serialize(root);
};
/**
* @param {ol.expr.Call|string|number} value The value write out.
* @param {Element} node The node to append to.
* @return {Element} The node to which was appended.
* @protected
*/
ol.parser.ogc.Filter_v1.prototype.writeOgcExpression = function(value, node) {
if (value instanceof ol.expr.Call) {
this.writeNode('Function', value, null, node);
} else {
this.writeNode('Literal', value, null, node);
}
return node;
};
/**
* @param {ol.expr.Logical} filter The filter to retrieve the sub filters from.
* @param {Array.<ol.expr.Expression>} subFilters The sub filters retrieved.
* @private
*/
ol.parser.ogc.Filter_v1.prototype.getSubfiltersForLogical_ = function(filter,
subFilters) {
var operator = filter.getOperator();
var filters = new Array(2);
filters[0] = filter.getLeft();
filters[1] = filter.getRight();
for (var i = 0; i < 2; ++i) {
if (filters[i] instanceof ol.expr.Logical && filters[i].getOperator() ===
operator) {
this.getSubfiltersForLogical_(filters[i], subFilters);
} else {
subFilters.push(filters[i]);
}
}
};
/**
* Since ol.expr.Logical can only have a left and a right, we need to nest
* sub filters coming from OGC Filter.
* @param {Array.<ol.expr.Expression>} filters The filters to aggregate.
* @param {ol.expr.LogicalOp} operator The logical operator to use.
* @return {ol.expr.Logical} The logical filter created.
* @private
*/
ol.parser.ogc.Filter_v1.prototype.aggregateLogical_ = function(filters,
operator) {
var subFilters = [];
var newFilters = [];
// we only need to do this if we have more than 2 items
if (filters.length > 2) {
while (filters.length) {
subFilters.push(filters.pop());
if (subFilters.length === 2) {
newFilters.push(new ol.expr.Logical(operator,
subFilters[0], subFilters[1]));
goog.array.clear(subFilters);
}
}
// there could be a single item left now
if (subFilters.length === 1) {
newFilters.push(subFilters[0]);
}
return this.aggregateLogical_(newFilters, operator);
} else {
return new ol.expr.Logical(operator, filters[0], filters[1]);
}
};
/**
* @param {ol.parser.ogc.GML_v2|ol.parser.ogc.GML_v3} gml The GML parser to
* use.
* @protected
*/
ol.parser.ogc.Filter_v1.prototype.setGmlParser = function(gml) {
this.gml_ = gml;
for (var uri in this.gml_.readers) {
for (var key in this.gml_.readers[uri]) {
if (!goog.isDef(this.readers[uri])) {
this.readers[uri] = {};
}
this.readers[uri][key] = goog.bind(this.gml_.readers[uri][key],
this.gml_);
}
}
for (uri in this.gml_.writers) {
for (key in this.gml_.writers[uri]) {
if (!goog.isDef(this.writers[uri])) {
this.writers[uri] = {};
}
this.writers[uri][key] = goog.bind(this.gml_.writers[uri][key],
this.gml_);
}
}
};

View File

@@ -0,0 +1,167 @@
goog.provide('ol.parser.ogc.Filter_v1_0_0');
goog.require('goog.object');
goog.require('ol.expr');
goog.require('ol.expr.Call');
goog.require('ol.expr.Comparison');
goog.require('ol.expr.ComparisonOp');
goog.require('ol.expr.Identifier');
goog.require('ol.expr.functions');
goog.require('ol.geom.Geometry');
goog.require('ol.parser.ogc.Filter_v1');
goog.require('ol.parser.ogc.GML_v2');
/**
* @constructor
* @extends {ol.parser.ogc.Filter_v1}
*/
ol.parser.ogc.Filter_v1_0_0 = function() {
goog.base(this);
this.version = '1.0.0';
this.schemaLocation = 'http://www.opengis.net/ogc ' +
'http://schemas.opengis.net/filter/1.0.0/filter.xsd';
goog.object.extend(this.readers['http://www.opengis.net/ogc'], {
'PropertyIsEqualTo': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
obj['filters'].push(new ol.expr.Comparison(
ol.expr.ComparisonOp.EQ,
container['property'],
container['value']));
},
'PropertyIsNotEqualTo': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
obj['filters'].push(new ol.expr.Comparison(
ol.expr.ComparisonOp.NEQ,
container['property'],
container['value']));
},
'PropertyIsLike': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
var args = [];
args.push(container['property'], container['value'],
node.getAttribute('wildCard'),
node.getAttribute('singleChar'),
node.getAttribute('escape')
);
obj['filters'].push(new ol.expr.Call(
new ol.expr.Identifier(ol.expr.functions.LIKE), args));
}
});
goog.object.extend(this.writers['http://www.opengis.net/ogc'], {
'PropertyIsEqualTo': function(filter) {
var node = this.createElementNS('ogc:PropertyIsEqualTo');
var property = filter.getLeft();
if (goog.isDef(property)) {
this.writeNode('PropertyName', property, null, node);
}
this.writeOgcExpression(filter.getRight(), node);
return node;
},
'PropertyIsNotEqualTo': function(filter) {
var node = this.createElementNS('ogc:PropertyIsNotEqualTo');
var property = filter.getLeft();
if (goog.isDef(property)) {
this.writeNode('PropertyName', property, null, node);
}
this.writeOgcExpression(filter.getRight(), node);
return node;
},
'PropertyIsLike': function(filter) {
var node = this.createElementNS('ogc:PropertyIsLike');
var args = filter.getArgs();
node.setAttribute('wildCard', args[2]);
node.setAttribute('singleChar', args[3]);
node.setAttribute('escape', args[4]);
var property = args[0];
if (goog.isDef(property)) {
this.writeNode('PropertyName', property, null, node);
}
this.writeNode('Literal', args[1], null, node);
return node;
},
'BBOX': function(filter) {
var node = this.createElementNS('ogc:BBOX');
var args = filter.getArgs();
var property = args[5], bbox = [args[0], args[1], args[2], args[3]],
projection = args[4];
// PropertyName is mandatory in 1.0.0, but e.g. GeoServer also
// accepts filters without it.
if (goog.isDefAndNotNull(property)) {
this.writeNode('PropertyName', property, null, node);
}
var box = this.writeNode('Box', bbox,
'http://www.opengis.net/gml');
if (goog.isDefAndNotNull(projection)) {
box.setAttribute('srsName', projection);
}
node.appendChild(box);
return node;
}
});
this.setGmlParser(new ol.parser.ogc.GML_v2({featureNS: 'http://foo'}));
};
goog.inherits(ol.parser.ogc.Filter_v1_0_0,
ol.parser.ogc.Filter_v1);
/**
* @param {ol.expr.Call} filter The filter to write out.
* @param {string} name The name of the spatial operator.
* @return {Element} The node created.
* @private
*/
ol.parser.ogc.Filter_v1_0_0.prototype.writeSpatial_ = function(filter, name) {
var node = this.createElementNS('ogc:' + name);
var args = filter.getArgs();
var property, geom = null, bbox, call, projection;
if (goog.isNumber(args[0])) {
bbox = [args[0], args[1], args[2], args[3]];
projection = args[4];
property = args[5];
} else if (args[0] instanceof ol.geom.Geometry) {
geom = args[0];
if (name === 'DWithin') {
projection = args[3];
property = args[4];
} else {
projection = args[1];
property = args[2];
}
} else if (args[0] instanceof ol.expr.Call) {
call = args[0];
if (name === 'DWithin') {
projection = args[3];
property = args[4];
} else {
projection = args[1];
property = args[2];
}
}
if (goog.isDefAndNotNull(property)) {
this.writeNode('PropertyName', property, null, node);
}
if (goog.isDef(call)) {
this.writeNode('Function', call, null, node);
} else {
var child;
if (geom !== null) {
child = this.writeNode('_geometry', geom,
this.gml_.featureNS).firstChild;
} else if (bbox.length === 4) {
child = this.writeNode('Box', bbox,
'http://www.opengis.net/gml');
}
if (goog.isDef(child)) {
if (goog.isDef(projection)) {
child.setAttribute('srsName', projection);
}
node.appendChild(child);
}
}
return node;
};

View File

@@ -0,0 +1,217 @@
goog.provide('ol.parser.ogc.Filter_v1_1_0');
goog.require('goog.object');
goog.require('ol.expr');
goog.require('ol.expr.Call');
goog.require('ol.expr.Comparison');
goog.require('ol.expr.ComparisonOp');
goog.require('ol.expr.Identifier');
goog.require('ol.expr.functions');
goog.require('ol.geom.Geometry');
goog.require('ol.parser.ogc.Filter_v1');
goog.require('ol.parser.ogc.GML_v3');
/**
* @constructor
* @extends {ol.parser.ogc.Filter_v1}
*/
ol.parser.ogc.Filter_v1_1_0 = function() {
goog.base(this);
this.version = '1.1.0';
this.schemaLocation = 'http://www.opengis.net/ogc ' +
'http://schemas.opengis.net/filter/1.1.0/filter.xsd';
goog.object.extend(this.readers['http://www.opengis.net/ogc'], {
'PropertyIsEqualTo': function(node, obj) {
var matchCase = node.getAttribute('matchCase');
var container = {}, filter;
this.readChildNodes(node, container);
if (matchCase === 'false' || matchCase === '0') {
filter = new ol.expr.Call(new ol.expr.Identifier(ol.expr.functions.IEQ),
[container['property'], container['value']]);
} else {
filter = new ol.expr.Comparison(
ol.expr.ComparisonOp.EQ,
container['property'],
container['value']);
}
obj['filters'].push(filter);
},
'PropertyIsNotEqualTo': function(node, obj) {
var matchCase = node.getAttribute('matchCase');
var container = {}, filter;
this.readChildNodes(node, container);
if (matchCase === 'false' || matchCase === '0') {
filter = new ol.expr.Call(new ol.expr.Identifier(
ol.expr.functions.INEQ),
[container['property'], container['value']]);
} else {
filter = new ol.expr.Comparison(
ol.expr.ComparisonOp.NEQ,
container['property'],
container['value']);
}
obj['filters'].push(filter);
},
'PropertyIsLike': function(node, obj) {
var container = {};
this.readChildNodes(node, container);
var args = [];
args.push(container['property'], container['value'],
node.getAttribute('wildCard'),
node.getAttribute('singleChar'),
node.getAttribute('escapeChar'),
node.getAttribute('matchCase'));
obj['filters'].push(new ol.expr.Call(
new ol.expr.Identifier(ol.expr.functions.LIKE), args));
}
});
goog.object.extend(this.writers['http://www.opengis.net/ogc'], {
'PropertyIsEqualTo': function(filter) {
var node = this.createElementNS('ogc:PropertyIsEqualTo');
var property, value;
if (filter instanceof ol.expr.Call) {
var args = filter.getArgs();
property = args[0];
value = args[1];
node.setAttribute('matchCase', false);
} else {
property = filter.getLeft();
value = filter.getRight();
}
this.writeNode('PropertyName', property, null, node);
this.writeOgcExpression(value, node);
return node;
},
'PropertyIsNotEqualTo': function(filter) {
var node = this.createElementNS('ogc:PropertyIsNotEqualTo');
var property, value;
if (filter instanceof ol.expr.Call) {
var args = filter.getArgs();
property = args[0];
value = args[1];
node.setAttribute('matchCase', false);
} else {
property = filter.getLeft();
value = filter.getRight();
}
this.writeNode('PropertyName', property, null, node);
this.writeOgcExpression(value, node);
return node;
},
'PropertyIsLike': function(filter) {
var node = this.createElementNS('ogc:PropertyIsLike');
var args = filter.getArgs();
node.setAttribute('wildCard', args[2]);
node.setAttribute('singleChar', args[3]);
node.setAttribute('escapeChar', args[4]);
if (goog.isDefAndNotNull(args[5])) {
node.setAttribute('matchCase', args[5]);
}
var property = args[0];
if (goog.isDef(property)) {
this.writeNode('PropertyName', property, null, node);
}
this.writeNode('Literal', args[1], null, node);
return node;
},
'BBOX': function(filter) {
var node = this.createElementNS('ogc:BBOX');
var args = filter.getArgs();
var property = args[5], bbox = [args[0], args[1], args[2], args[3]],
projection = args[4];
// PropertyName is optional in 1.1.0
if (goog.isDefAndNotNull(property)) {
this.writeNode('PropertyName', property, null, node);
}
var box = this.writeNode('Envelope', bbox,
'http://www.opengis.net/gml');
if (goog.isDefAndNotNull(projection)) {
box.setAttribute('srsName', projection);
}
node.appendChild(box);
return node;
},
'SortBy': function(sortProperties) {
var node = this.createElementNS('ogc:SortBy');
for (var i = 0, l = sortProperties.length; i < l; i++) {
this.writeNode('SortProperty', sortProperties[i], null, node);
}
return node;
},
'SortProperty': function(sortProperty) {
var node = this.createElementNS('ogc:SortProperty');
this.writeNode('PropertyName', sortProperty['property'], null, node);
this.writeNode('SortOrder',
(sortProperty['order'] == 'DESC') ? 'DESC' : 'ASC', null, node);
return node;
},
'SortOrder': function(value) {
var node = this.createElementNS('ogc:SortOrder');
node.appendChild(this.createTextNode(value));
return node;
}
});
this.setGmlParser(new ol.parser.ogc.GML_v3());
};
goog.inherits(ol.parser.ogc.Filter_v1_1_0,
ol.parser.ogc.Filter_v1);
/**
* @param {ol.expr.Call} filter The filter to write out.
* @param {string} name The name of the spatial operator.
* @return {Element} The node created.
* @private
*/
ol.parser.ogc.Filter_v1_1_0.prototype.writeSpatial_ = function(filter, name) {
var node = this.createElementNS('ogc:' + name);
var args = filter.getArgs();
var property, geom = null, bbox, call, projection;
if (goog.isNumber(args[0])) {
bbox = [args[0], args[1], args[2], args[3]];
projection = args[4];
property = args[5];
} else if (args[0] instanceof ol.geom.Geometry) {
geom = args[0];
if (name === 'DWithin') {
projection = args[3];
property = args[4];
} else {
projection = args[1];
property = args[2];
}
} else if (args[0] instanceof ol.expr.Call) {
call = args[0];
if (name === 'DWithin') {
projection = args[3];
property = args[4];
} else {
projection = args[1];
property = args[2];
}
}
if (goog.isDefAndNotNull(property)) {
this.writeNode('PropertyName', property, null, node);
}
if (goog.isDef(call)) {
this.writeNode('Function', call, null, node);
} else {
var child;
if (geom !== null) {
child = this.writeNode('_geometry', geom,
this.gml_.featureNS).firstChild;
} else if (bbox.length === 4) {
child = this.writeNode('Envelope', bbox,
'http://www.opengis.net/gml');
}
if (goog.isDef(child)) {
if (goog.isDef(projection)) {
child.setAttribute('srsName', projection);
}
node.appendChild(child);
}
}
return node;
};

View File

@@ -284,7 +284,7 @@ ol.parser.ogc.GML = function(opt_options) {
sharedVertices = callback(feature, geom.type);
}
}
var geometry = this.createGeometry_({geometry: geom},
var geometry = this.createGeometry({geometry: geom},
sharedVertices);
if (goog.isDef(geometry)) {
feature.setGeometry(geometry);
@@ -434,7 +434,7 @@ ol.parser.ogc.GML = function(opt_options) {
} else if (type === ol.geom.GeometryType.GEOMETRYCOLLECTION) {
child = this.writeNode('GeometryCollection', geometry, null, node);
}
if (goog.isDef(this.srsName)) {
if (goog.isDefAndNotNull(this.srsName)) {
this.setAttributeNS(child, null, 'srsName', this.srsName);
}
return node;
@@ -503,13 +503,12 @@ ol.parser.ogc.GML.prototype.readNode = function(node, obj, opt_first) {
/**
* @private
* @param {Object} container Geometry container.
* @param {ol.geom.SharedVertices=} opt_vertices Shared vertices.
* @return {ol.geom.Geometry} The geometry created.
*/
// TODO use a mixin since this is also used in the KML parser
ol.parser.ogc.GML.prototype.createGeometry_ = function(container,
ol.parser.ogc.GML.prototype.createGeometry = function(container,
opt_vertices) {
var geometry = null, coordinates, i, ii;
switch (container.geometry.type) {
@@ -553,7 +552,7 @@ ol.parser.ogc.GML.prototype.createGeometry_ = function(container,
case ol.geom.GeometryType.GEOMETRYCOLLECTION:
var geometries = [];
for (i = 0, ii = container.geometry.parts.length; i < ii; i++) {
geometries.push(this.createGeometry_({
geometries.push(this.createGeometry({
geometry: container.geometry.parts[i]
}, opt_vertices));
}

View File

@@ -29,6 +29,7 @@ ol.parser.ogc.GML_v2 = function(opt_options) {
'Box': function(node, container) {
var coordinates = [];
this.readChildNodes(node, coordinates);
container.projection = node.getAttribute('srsName');
container.bounds = [coordinates[0][0][0], coordinates[0][1][0],
coordinates[0][0][1], coordinates[0][1][1]];
}
@@ -90,10 +91,10 @@ ol.parser.ogc.GML_v2 = function(opt_options) {
},
'Box': function(extent) {
var node = this.createElementNS('gml:Box');
this.writeNode('coordinates', [[extent.minX, extent.minY],
[extent.maxX, extent.maxY]], null, node);
this.writeNode('coordinates', [[extent[0], extent[1]],
[extent[2], extent[3]]], null, node);
// srsName attribute is optional for gml:Box
if (goog.isDef(this.srsName)) {
if (goog.isDefAndNotNull(this.srsName)) {
node.setAttribute('srsName', this.srsName);
}
return node;

View File

@@ -55,7 +55,7 @@ ol.parser.ogc.GML_v3 = function(opt_options) {
} else if (type === ol.geom.GeometryType.GEOMETRYCOLLECTION) {
child = this.writeNode('MultiGeometry', geometry, null, node);
}
if (goog.isDef(this.srsName)) {
if (goog.isDefAndNotNull(this.srsName)) {
this.setAttributeNS(child, null, 'srsName', this.srsName);
}
return node;
@@ -206,6 +206,7 @@ ol.parser.ogc.GML_v3 = function(opt_options) {
'Envelope': function(node, container) {
var coordinates = [];
this.readChildNodes(node, coordinates);
container.projection = node.getAttribute('srsName');
container.bounds = [coordinates[0][0][0][0], coordinates[1][0][0][0],
coordinates[0][0][0][1], coordinates[1][0][0][1]];
},
@@ -374,9 +375,9 @@ ol.parser.ogc.GML_v3 = function(opt_options) {
// only 2d for simple features profile
var pos;
if (this.axisOrientation.substr(0, 2) === 'en') {
pos = (bounds.left + ' ' + bounds.bottom);
pos = (bounds[0] + ' ' + bounds[2]);
} else {
pos = (bounds.bottom + ' ' + bounds.left);
pos = (bounds[2] + ' ' + bounds[0]);
}
var node = this.createElementNS('gml:lowerCorner');
node.appendChild(this.createTextNode(pos));
@@ -386,9 +387,9 @@ ol.parser.ogc.GML_v3 = function(opt_options) {
// only 2d for simple features profile
var pos;
if (this.axisOrientation.substr(0, 2) === 'en') {
pos = (bounds.right + ' ' + bounds.top);
pos = (bounds[1] + ' ' + bounds[3]);
} else {
pos = (bounds.top + ' ' + bounds.right);
pos = (bounds[3] + ' ' + bounds[1]);
}
var node = this.createElementNS('gml:upperCorner');
node.appendChild(this.createTextNode(pos));

View File

@@ -182,8 +182,9 @@ ol.parser.XML.prototype.createElementNS = function(name, opt_uri) {
* results to a node.
*
* @param {string} name The name of a node to generate. Only use a local name.
* @param {Object} obj Structure containing data for the writer.
* @param {string=} opt_uri The name space uri to which the node belongs.
* @param {Object|string|number} obj Structure containing data for the writer.
* @param {?string=} opt_uri The name space uri to which the node
* belongs.
* @param {Element=} opt_parent Result will be appended to this node. If no
* parent is supplied, the node will not be appended to anything.
* @return {?Element} The child node.
@@ -273,3 +274,22 @@ ol.parser.XML.prototype.serialize = function(node) {
return goog.dom.xml.serialize(node);
}
};
/**
* Create a document fragment node that can be appended to another node
* created by createElementNS. This will call
* document.createDocumentFragment outside of IE. In IE, the ActiveX
* object's createDocumentFragment method is used.
*
* @return {Element} A document fragment.
*/
ol.parser.XML.prototype.createDocumentFragment = function() {
var element;
if (this.xmldom) {
element = this.xmldom.createDocumentFragment();
} else {
element = document.createDocumentFragment();
}
return element;
};