Add Insert, Update and Delete writers

This change also adds some type annotations for better type
checking, introduces different write options for writing
transactions and queries, and provides new writeGetFeature and
writeTransaction methods.
This commit is contained in:
ahocevar
2013-12-06 10:57:58 +01:00
parent f539eb140f
commit b3b9add06e
5 changed files with 324 additions and 66 deletions

View File

@@ -595,6 +595,32 @@
* calculations.
*/
/**
* @typedef {Object} ol.parser.WFSWriteGetFeatureOptions
* @property {string} featureNS The namespace URI used for features.
* @property {string} featurePrefix The prefix for the feature namespace.
* @property {Array.<string>} featureTypes The feature type names.
* @property {string|undefined} srsName SRS name. No srsName attribute will be
* set on geometries when this is not provided.
* @property {string|undefined} handle Handle.
* @property {string|undefined} outputFormat Output format.
* @property {Array.<Object>} nativeElements Native elements. Currently not
* supported.
* @property {number} maxFeatures Maximum number of features to fetch.
*/
/**
* @typedef {Object} ol.parser.WFSWriteTransactionOptions
* @property {string} featureNS The namespace URI used for features.
* @property {string} featurePrefix The prefix for the feature namespace.
* @property {string} featureType The feature type name.
* @property {string|undefined} srsName SRS name. No srsName attribute will be
* set on geometries when this is not provided.
* @property {string|undefined} handle Handle.
* @property {Array.<Object>} nativeElements Native elements. Currently not
* supported.
*/
/**
* @typedef {Object} ol.source.BingMapsOptions
* @property {string|undefined} culture Culture code. Default is `en-us`.

View File

@@ -1,31 +1,22 @@
goog.provide('ol.parser.ogc.WFS_v1');
goog.require('goog.asserts');
goog.require('goog.dom.xml');
goog.require('ol.expr.Call');
goog.require('ol.expr.Identifier');
goog.require('ol.expr.Literal');
goog.require('ol.geom.Geometry');
goog.require('ol.parser.XML');
/**
* @typedef {{featureNS: string,
featurePrefix: string,
featureTypes: Array.<string>,
handle: string,
outputFormat: string,
nativeElements: Array.<{
vendorId: string,
safeToIgnore: boolean,
value: string
}>,
maxFeatures: number}}
*/
ol.parser.WFSWriteOptions;
/**
* @constructor
* @extends {ol.parser.XML}
* @param {Object=} opt_options Options which will be set on this object.
*/
ol.parser.ogc.WFS_v1 = function() {
ol.parser.ogc.WFS_v1 = function(opt_options) {
this.defaultNamespaceURI = 'http://www.opengis.net/wfs';
// TODO set errorProperty
this.readers = {};
this.readers[this.defaultNamespaceURI] = {
@@ -36,8 +27,13 @@ ol.parser.ogc.WFS_v1 = function() {
};
this.writers = {};
this.writers[this.defaultNamespaceURI] = {
/**
* @param {ol.parser.WFSWriteGetFeatureOptions} options Options.
* @return {{node: Node,
* options: ol.parser.WFSWriteGetFeatureOptions}} Object.
* @this {ol.parser.XML}
*/
'GetFeature': function(options) {
options = /** @type {ol.parser.WFSWriteOptions} */(options);
var node = this.createElementNS('wfs:GetFeature');
node.setAttribute('service', 'WFS');
node.setAttribute('version', this.version);
@@ -61,29 +57,41 @@ ol.parser.ogc.WFS_v1 = function() {
'xsi:schemaLocation', this.schemaLocation);
return {node: node, options: options};
},
/**
* @param {{inserts: Array.<ol.Feature>,
* updates: Array.<ol.Feature>,
* deletes: Array.<ol.Feature>,
* options: ol.parser.WFSWriteTransactionOptions}} obj Object.
* @return {Element} Node.
* @this {ol.parser.XML}
*/
'Transaction': function(obj) {
obj = obj || {};
var options = /** {ol.parser.WFSWriteOptions} */(obj.options || {});
var options = obj.options;
this.setFeatureType(options.featureType);
this.setFeatureNS(options.featureNS);
if (goog.isDef(options.srsName)) {
this.setSrsName(options.srsName);
}
var node = this.createElementNS('wfs:Transaction');
node.setAttribute('service', 'WFS');
node.setAttribute('version', this.version);
if (goog.isDef(options.handle)) {
node.setAttribute('handle', options.handle);
}
var i, ii;
var features = obj.features;
if (goog.isDefAndNotNull(features)) {
// TODO implement multi option for geometry types
var name, feature;
for (i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
// TODO Update (use feature.getOriginal())
// TODO Insert and Delete
if (goog.isDef(name)) {
this.writeNode(name, {
feature: feature,
options: options
}, null, node);
var i, ii, features, feature;
var operations = {
'Insert': obj.inserts,
'Update': obj.updates,
'Delete': obj.deletes
};
for (var name in operations) {
features = operations[name];
if (!goog.isNull(features)) {
// TODO implement multi option for geometry types
for (i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
this.writeNode(name, {feature: feature, options: options}, null,
node);
}
}
}
@@ -94,12 +102,147 @@ ol.parser.ogc.WFS_v1 = function() {
}
return node;
},
/**
* @param {{vendorId: string, safeToIgnore: boolean, value: string}}
* nativeElement Native element.
* @return {Node} Node.
* @this {ol.parser.XML}
*/
'Native': function(nativeElement) {
var node = this.createElementNS('wfs:Native');
node.setAttribute('vendorId', nativeElement.vendorId);
node.setAttribute('safeToIgnore', nativeElement.safeToIgnore);
node.appendChild(this.createTextNode(nativeElement.value));
return node;
},
/**
* @param {{feature: ol.Feature,
* options: ol.parser.WFSWriteTransactionOptions}} obj Object.
* @return {Element} Node.
* @this {ol.parser.XML}
*/
'Insert': function(obj) {
var feature = obj.feature;
var options = obj.options;
var node = this.createElementNS('wfs:Insert');
if (goog.isDef(options) && goog.isDef(options.handle)) {
this.setAttributeNS(node, this.defaultNamespaceURI, 'handle',
options.handle);
}
if (goog.isDef(options.srsName)) {
this.setSrsName(options.srsName);
}
this.writeNode('_typeName', feature, options.featureNS, node);
return node;
},
/**
* @param {{feature: ol.Feature,
* options: ol.parser.WFSWriteTransactionOptions}} obj Object.
* @return {Element} Node.
* @this {ol.parser.XML}
*/
'Update': function(obj) {
var feature = obj.feature;
var options = obj.options;
var node = this.createElementNS('wfs:Update');
this.setAttributeNS(node, this.defaultNamespaceURI, 'typeName',
(goog.isDef(options.featureNS) ? options.featurePrefix + ':' : '') +
options.featureType);
if (goog.isDef(options.handle)) {
this.setAttributeNS(node, this.defaultNamespaceURI, 'handle',
options.handle);
}
if (goog.isDef(options.featureNS)) {
node.setAttribute('xmlns:' + options.featurePrefix, options.featureNS);
}
// add in fields
var original = feature.getOriginal();
var originalAttributes = goog.isNull(original) ?
undefined : original.getAttributes();
var attributes = feature.getAttributes();
var attribute;
for (var key in attributes) {
attribute = attributes[key];
// TODO Only add geometries whose values have changed
if (goog.isDef(attribute) && (attribute instanceof ol.geom.Geometry ||
(!goog.isDef(originalAttributes) ||
attribute != originalAttributes[key]))) {
this.writeNode('Property', {name: key, value: attribute}, null, node);
}
}
// add feature id filter
var fid = feature.getId();
goog.asserts.assert(goog.isDef(fid));
this.writeNode('Filter', new ol.expr.Call(new ol.expr.Identifier(
ol.expr.functions.FID), [new ol.expr.Literal(fid)]),
'http://www.opengis.net/ogc', node);
return node;
},
'Property': function(obj) {
var node = this.createElementNS('wfs:Property');
this.writeNode('Name', obj.name, null, node);
if (!goog.isNull(obj.value)) {
this.writeNode('Value', obj.value, null, node);
}
return node;
},
/**
* @param {string} name Name.
* @return {Element} Node.
* @this {ol.parser.XML}
*/
'Name': function(name) {
var node = this.createElementNS('wfs:Name');
node.appendChild(this.createTextNode(name));
return node;
},
/**
* @param {string|ol.geom.Geometry} obj Object.
* @return {Element} Node.
* @this {ol.parser.XML}
*/
'Value': function(obj) {
var node;
if (obj instanceof ol.geom.Geometry) {
node = this.createElementNS('wfs:Value');
node.appendChild(
this.getFilterParser().getGmlParser().writeGeometry(obj));
} else {
goog.asserts.assertString(obj);
node = this.createElementNS('wfs:Value');
node.appendChild(this.createTextNode(obj));
}
return node;
},
/**
* @param {{feature: ol.Feature,
* options: ol.parser.WFSWriteTransactionOptions}} obj Object.
* @return {Element} Node.
* @this {ol.parser.ogc.GML}
*/
'Delete': function(obj) {
var feature = obj.feature;
var options = obj.options;
var node = this.createElementNS('wfs:Delete');
this.setAttributeNS(node, this.defaultNamespaceURI, 'typeName',
(goog.isDef(options.featureNS) ? options.featurePrefix + ':' : '') +
options.featureType);
if (goog.isDef(options.handle)) {
this.setAttributeNS(node, options.defaultNamespaceURI, 'handle',
options.handle);
}
if (goog.isDef(options.featureNS)) {
node.setAttribute('xmlns:' + options.featurePrefix, options.featureNS);
}
var fid = feature.getId();
goog.asserts.assert(goog.isDef(fid));
this.writeNode('Filter', new ol.expr.Call(new ol.expr.Identifier(
ol.expr.functions.FID), [new ol.expr.Literal(fid)]),
'http://www.opengis.net/ogc', node);
return node;
}
};
goog.base(this);
@@ -204,13 +347,27 @@ ol.parser.ogc.WFS_v1.prototype.read = function(data) {
/**
* @param {Array.<ol.Feature>} features The features to write out.
* @param {ol.parser.WFSWriteOptions} options Write options.
* @param {ol.parser.WFSWriteGetFeatureOptions} options Options.
* @return {string} A serialized WFS GetFeature query.
*/
ol.parser.ogc.WFS_v1.prototype.writeGetFeature = function(options) {
var root = this.writers[this.defaultNamespaceURI]['GetFeature']
.call(this, options);
return this.serialize(root);
};
/**
* @param {Array.<ol.Feature>} inserts The features to insert.
* @param {Array.<ol.Feature>} updates The features to update.
* @param {Array.<ol.Feature>} deletes The features to delete.
* @param {ol.parser.WFSWriteTransactionOptions} options Write options.
* @return {string} A serialized WFS transaction.
*/
ol.parser.ogc.WFS_v1.prototype.write = function(features, options) {
var root = this.writeNode('Transaction', {features: features,
options: options});
ol.parser.ogc.WFS_v1.prototype.writeTransaction =
function(inserts, updates, deletes, options) {
var root = this.writeNode('Transaction', {inserts: inserts,
updates: updates, deletes: deletes, options: options});
this.setAttributeNS(
root, 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation', this.schemaLocation);

View File

@@ -19,17 +19,15 @@ describe('ol.parser.ogc.WFS', function() {
var url = 'spec/ol/parser/ogc/xml/wfs_v1/GetFeature.xml';
afterLoadXml(url, function(xml) {
var p = new ol.parser.ogc.WFS_v1_0_0();
var output = p.writers[p.defaultNamespaceURI]['GetFeature'].
apply(p, [{
featureNS: 'http://www.openplans.org/topp',
featureTypes: ['states'],
featurePrefix: 'topp',
handle: 'handle_g',
maxFeatures: 1,
outputFormat: 'json'
}
]);
expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml);
var output = p.writeGetFeature({
featureNS: 'http://www.openplans.org/topp',
featureTypes: ['states'],
featurePrefix: 'topp',
handle: 'handle_g',
maxFeatures: 1,
outputFormat: 'json'
});
expect(goog.dom.xml.loadXml(output)).to.xmleql(xml);
done();
});
});
@@ -38,12 +36,44 @@ describe('ol.parser.ogc.WFS', function() {
var url = 'spec/ol/parser/ogc/xml/wfs_v1/Transaction.xml';
afterLoadXml(url, function(xml) {
var p = new ol.parser.ogc.WFS_v1_0_0();
var output = p.writers[p.defaultNamespaceURI]['Transaction'].
apply(p, [{
options: {handle: 'handle_t'}
}
]);
expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml);
var output = p.writeTransaction(null, null, null, {handle: 'handle_t'});
expect(goog.dom.xml.loadXml(output)).to.xmleql(xml);
done();
});
});
it('handles writing out transactions', function(done) {
var url = 'spec/ol/parser/ogc/xml/wfs_v1/TransactionMulti.xml';
afterLoadXml(url, function(xml) {
var parser = new ol.parser.ogc.WFS_v1_0_0();
var insertFeature = new ol.Feature({
the_geom: new ol.geom.MultiPoint([[1, 2]]),
foo: 'bar',
nul: null
});
var inserts = [insertFeature];
var updateFeature = new ol.Feature({
the_geom: new ol.geom.MultiPoint([[1, 2]]),
foo: 'bar',
// null value gets Property element with no Value
nul: null,
// undefined value means don't create a Property element
unwritten: undefined
});
updateFeature.setId('fid.42');
var updates = [updateFeature];
var deleteFeature = new ol.Feature();
deleteFeature.setId('fid.37');
var deletes = [deleteFeature];
var output = parser.writeTransaction(inserts, updates, deletes, {
featureNS: 'http://www.openplans.org/topp',
featureType: 'states',
featurePrefix: 'topp'
});
expect(goog.dom.xml.loadXml(output)).to.xmleql(xml);
done();
});
});
@@ -52,7 +82,7 @@ describe('ol.parser.ogc.WFS', function() {
var url = 'spec/ol/parser/ogc/xml/wfs_v1/Native.xml';
afterLoadXml(url, function(xml) {
var p = new ol.parser.ogc.WFS_v1_1_0();
var output = p.write(null, {nativeElements: [{
var output = p.writeTransaction(null, null, null, {nativeElements: [{
vendorId: 'ORACLE',
safeToIgnore: true,
value: 'ALTER SESSION ENABLE PARALLEL DML'
@@ -70,14 +100,12 @@ describe('ol.parser.ogc.WFS', function() {
var url = 'spec/ol/parser/ogc/xml/wfs_v1/GetFeatureMultiple.xml';
afterLoadXml(url, function(xml) {
var p = new ol.parser.ogc.WFS_v1_0_0();
var output = p.writers[p.defaultNamespaceURI]['GetFeature'].
apply(p, [{
featureNS: 'http://www.openplans.org/topp',
featureTypes: ['states', 'cities'],
featurePrefix: 'topp'
}
]);
expect(goog.dom.xml.loadXml(p.serialize(output))).to.xmleql(xml);
var output = p.writeGetFeature({
featureNS: 'http://www.openplans.org/topp',
featureTypes: ['states', 'cities'],
featurePrefix: 'topp'
});
expect(goog.dom.xml.loadXml(output)).to.xmleql(xml);
done();
});
});
@@ -87,6 +115,8 @@ describe('ol.parser.ogc.WFS', function() {
});
goog.require('goog.dom.xml');
goog.require('ol.Feature');
goog.require('ol.geom.MultiPoint');
goog.require('ol.parser.ogc.WFS');
goog.require('ol.parser.ogc.WFS_v1_0_0');
goog.require('ol.parser.ogc.WFS_v1_1_0');

View File

@@ -1 +1 @@
<wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0" handle="handle_t" />
<wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" service="WFS" version="1.0.0" handle="handle_t" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"/>

View File

@@ -0,0 +1,45 @@
<wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" service="WFS" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd">
<wfs:Insert>
<feature:states xmlns:feature="http://www.openplans.org/topp">
<feature:the_geom>
<gml:MultiPoint xmlns:gml="http://www.opengis.net/gml">
<gml:pointMember>
<gml:Point>
<gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
</gml:Point>
</gml:pointMember>
</gml:MultiPoint>
</feature:the_geom>
<feature:foo>bar</feature:foo>
</feature:states>
</wfs:Insert>
<wfs:Update xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
<wfs:Property>
<wfs:Name>the_geom</wfs:Name>
<wfs:Value>
<gml:MultiPoint xmlns:gml="http://www.opengis.net/gml">
<gml:pointMember>
<gml:Point>
<gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
</gml:Point>
</gml:pointMember>
</gml:MultiPoint>
</wfs:Value>
</wfs:Property>
<wfs:Property>
<wfs:Name>foo</wfs:Name>
<wfs:Value>bar</wfs:Value>
</wfs:Property>
<wfs:Property>
<wfs:Name>nul</wfs:Name>
</wfs:Property>
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:FeatureId fid="fid.42"/>
</ogc:Filter>
</wfs:Update>
<wfs:Delete xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:FeatureId fid="fid.37"/>
</ogc:Filter>
</wfs:Delete>
</wfs:Transaction>