├── .gitignore ├── .babelrc.json ├── make-docs.js ├── .eslintrc.yml ├── validation ├── fixtures │ ├── mock_schema.xsd │ └── topp.xsd ├── formatXml.js ├── featureExamples.js └── test.js ├── src ├── filter.js ├── ensure.js ├── xml.js ├── typeDefs.js ├── utils.js └── index.js ├── .travis.yml ├── rollup.config.js ├── package.json ├── README.md ├── API.md └── dist ├── es6.es.js ├── es6.cjs.js ├── es6.umd.js ├── es5.es.js └── es5.cjs.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | dist/* 3 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": true 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "external-helpers" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /make-docs.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const jsdoc2md = require('jsdoc-to-markdown'); 3 | 4 | 5 | jsdoc2md.render({files: ['src/**']}).then( 6 | function(result) { 7 | fs.writeFileSync('API.md', result); 8 | console.log(result); 9 | return 0; 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | # for more enviromnent presets, see 3 | # https://eslint.org/docs/4.0.0/user-guide/configuring#specifying-environments 4 | browser: true 5 | commonjs: true 6 | es6: true 7 | node: true 8 | extends: google 9 | parserOptions: 10 | ecmaFeatures: 11 | experimentalObjectRestSpread: true 12 | ecmaVersion: 8 13 | sourceType: module 14 | rules: 15 | indent: 16 | - error 17 | - 2 18 | comma-dangle: 19 | - error 20 | - never 21 | -------------------------------------------------------------------------------- /validation/fixtures/mock_schema.xsd: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/filter.js: -------------------------------------------------------------------------------- 1 | import {idFilter} from './xml.js'; 2 | import {unpack} from './utils.js'; 3 | /** 4 | * Builds a filter from feature ids if one is not already input. 5 | * @function 6 | * @param {?String} filter a possible string filter 7 | * @param {Array} features an array of geojson feature objects 8 | * @param {Object} params an object of backup / override parameters 9 | * @return {String} A filter, or the input filter if it was a string. 10 | */ 11 | export function filter(filter, features, params) { 12 | if (!filter) { 13 | filter = ''; 14 | for (let feature of features) { 15 | let layer = unpack(feature, params); 16 | filter += idFilter(layer, feature.id); 17 | } 18 | return `${filter}`; 19 | } else { 20 | return filter; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | before_deploy: 5 | - touch .npmignore # make dist/ files visible 6 | deploy: 7 | skip_cleanup: true 8 | provider: npm 9 | email: kalt.steven@gmail.com 10 | api_key: 11 | secure: KyUmTE7BY8CaKSEhsEWwagfyp7L2F7lzTGVRNlhiXsKqTYz75MK0fCN3T6OERnnKeNGHBH94SAeSOC0vPM3p0SfsPh0saEZgGnKyDMP3XnbfWAN+9fg4Cdr+QXeNMzRmTkl0RtUg8xVJpLoSj3htB8O2vSdfRMTPDIwlizuxAi2DgrksNWRvGHCcw6h9M4bAG5sTrH00U5E3RrTl4h00eCGnRWmX0QA7x04C4V64KfHmaz66C9tA/B4/GLcP3zoP/uceW08Uqu9m7SBWYc2JlMTwK9/2DNfVu7RGl/IoT7DXJirsQPIJ95ZdFVnb+98wTq+LzPZ4ePVCJbiCmEoJ4gNIwl0CW4rxA30bmCWdt8aO7SF5Byk8I9eR/nTLE6DRlSSy//XS3q55ZMZwGgA9V7Bf1bZajnTxl1I5v7wvN29xeX+k2iSRWxitb/1dqxtOKfrveDpDa9z326ES48zI15u2B6/pgXCsR0Ozgwb6dHyNG2VQ2t+DRETY2nszzUNfKCgQ0gS5KoLZsxKNPKtoryrUyCmTTwiNrjDhduvCVlEtaApF6D74pwdBYl6M/MbyAF2fjLyo78mN73gzwi2OFah18w5duHtTBX7DqE58kMO5JUckoh5LoHpuwhnZmDiQwuxlhN+dVfpNpWr7dvBnD8UHEwx8EMHAT6jws0NUWtI= 12 | on: 13 | branch: master 14 | tags: true 15 | repo: SKalt/geojson-to-wfs-t-2 16 | -------------------------------------------------------------------------------- /validation/fixtures/topp.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import eslint from 'rollup-plugin-eslint'; 3 | import resolve from 'rollup-plugin-node-resolve'; 4 | import replace from 'rollup-plugin-replace'; 5 | import uglify from 'rollup-plugin-uglify'; 6 | const name = 'geojsonToWfst'; 7 | const base = { 8 | input: 'src/index.js', 9 | plugins: [ 10 | // commonjs(), 11 | eslint(), 12 | babel({ 13 | exclude: 'node_modules/!(geojson-to-gml-3)' 14 | }), 15 | resolve(), 16 | replace({ 17 | ENV: JSON.stringify(process.env.NODE_ENV || 'development') 18 | }), 19 | (process.env.NODE_ENV === 'production' && uglify()) 20 | ] 21 | }; 22 | let plugins = [...base.plugins]; 23 | plugins.splice(3, 1, babel({ 24 | exclude: 'node_modules/!(geojson-to-gml-3)', 25 | presets: [ 26 | [ 27 | '@babel/env', 28 | { 29 | useBuiltIns: 'entry', 30 | modules: false 31 | } 32 | ] 33 | ] 34 | })); 35 | 36 | export default [ 37 | Object.assign({}, base, { 38 | output: ['es', 'cjs', 'umd'].map( 39 | (format) => ({ 40 | format, 41 | name, 42 | file: `dist/es6.${format}.js` 43 | }) 44 | ) 45 | }), 46 | Object.assign({}, base, { 47 | plugins, 48 | output: ['cjs', 'umd'].map( 49 | (format) => ({ 50 | format, 51 | name, 52 | file: `dist/es5.${format}.js` 53 | }) 54 | ) 55 | }) 56 | ]; 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geojson-to-wfs-t-2", 3 | "version": "1.5.1", 4 | "description": "A library to convert geojson features to WFS-T-2.0 xml strings.", 5 | "main": "dist/es6.cjs.js", 6 | "module": "src/index.js", 7 | "scripts": { 8 | "docs": "node ./make-docs.js", 9 | "prebuild": "npm run docs", 10 | "build": "rollup -c", 11 | "pretest": "npm run build", 12 | "test": "cd validation; mocha --timeout 60000", 13 | "preversion": "node make-docs.js && npm run build && git add -f dist/*" 14 | }, 15 | "keywords": [ 16 | "xml", 17 | "wfs", 18 | "wfs-t", 19 | "transaction", 20 | "geoserver", 21 | "OGC", 22 | "gis" 23 | ], 24 | "author": "Steven Kalt", 25 | "license": " GPL-3.0", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/SKalt/geojson-to-wfs-t-2.git" 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "^7.3.3", 32 | "@babel/preset-env": "^7.3.1", 33 | "babel-plugin-external-helpers": "^6.22.0", 34 | "eslint": "^4.15.0", 35 | "eslint-config-google": "^0.8.0", 36 | "jsdoc-to-markdown": "^3.0.4", 37 | "mocha": "^5.2.0", 38 | "rollup": "^1.1.2", 39 | "rollup-plugin-babel": "^4.3.2", 40 | "rollup-plugin-commonjs": "^9.2.0", 41 | "rollup-plugin-eslint": "^3.0.0", 42 | "rollup-plugin-node-resolve": "^3.0.0", 43 | "rollup-plugin-replace": "^1.1.1", 44 | "rollup-plugin-uglify": "^2.0.1", 45 | "uglify-js": "^3.3.5", 46 | "xsd-schema-validator": "^0.5.0" 47 | }, 48 | "dependencies": { 49 | "geojson-to-gml-3": "^2.0.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ensure.js: -------------------------------------------------------------------------------- 1 | /** 2 | * common checks, coersions, and informative errors/ warnings 3 | * @module ensure 4 | */ 5 | 6 | /** 7 | * Ensures the result is an array. 8 | * @private 9 | * @function 10 | * @param {Feature|Feature[]|FeatureCollection} maybe a GeoJSON 11 | * FeatureCollection, Feature, or an array of Features. 12 | * @return {Feature[]} 13 | */ 14 | export const array = (...maybe)=> (maybe[0].features || [].concat(...maybe)) 15 | .filter(Boolean); 16 | /** 17 | * Ensures a layer.id format of an input id. 18 | * @function 19 | * @param {String} lyr layer name 20 | * @param {String} id id, possibly already in correct layer.id format. 21 | * @return {String} a correctly-formatted gml:id 22 | */ 23 | export const id = (lyr, id) => /\./.exec(id || '') ? id :`${lyr}.${id}`; 24 | /** 25 | * return a correctly-formatted typeName 26 | * @function 27 | * @param {String} ns namespace 28 | * @param {String} layer layer name 29 | * @param {String} typeName typeName to check 30 | * @return {String} a correctly-formatted typeName 31 | * @throws {Error} if typeName it cannot form a typeName from ns and layer 32 | */ 33 | export const typeName = (ns, layer, typeName) =>{ 34 | if (!typeName && !(ns && layer)) { 35 | throw new Error(`no typename possible: ${ 36 | JSON.stringify({typeName, ns, layer}, null, 2) 37 | }`); 38 | } 39 | return typeName || `${layer}`; 40 | }; 41 | 42 | 43 | // http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286 44 | /** 45 | * Checks the type of the input action 46 | * @function 47 | * @param {?String} action 48 | * @return {Boolean} whether the action is allowed 49 | */ 50 | const allowedActions = new Set([ 51 | 'replace', 'insertBefore', 'insertAfter', 'remove' 52 | ]); 53 | export const action = (action) => allowedActions.has(action); 54 | -------------------------------------------------------------------------------- /validation/formatXml.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 Stuart Powers 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | original gist at https://gist.github.com/sente/1083506/d2834134cd070dbcc08bf42ee27dabb746a1c54d 25 | */ 26 | 27 | function formatXml(xml) { 28 | var formatted = ''; 29 | var reg = /(>)(<)(\/*)/g; 30 | xml = xml.replace(reg, '$1\r\n$2$3'); 31 | var pad = 0; 32 | xml.split('\r\n').forEach(function(node, index) { 33 | var indent = 0; 34 | if (node.match( /.+<\/\w[^>]*>$/ )) { 35 | indent = 0; 36 | } else if (node.match( /^<\/\w/ )) { 37 | if (pad != 0) { 38 | pad -= 1; 39 | } 40 | } else if (node.match( /^<\w([^>]*[^\/])?>.*$/ )) { 41 | indent = 1; 42 | } else { 43 | indent = 0; 44 | } 45 | 46 | var padding = ''; 47 | for (var i = 0; i < pad; i++) { 48 | padding += ' '; 49 | } 50 | 51 | formatted += padding + node + '\r\n'; 52 | pad += indent; 53 | }); 54 | 55 | return formatted; 56 | } 57 | 58 | module.exports = formatXml; 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/xml.js: -------------------------------------------------------------------------------- 1 | import {id as ensureId} from './ensure'; 2 | /** 3 | * xml utilities. 4 | * @module xml 5 | */ 6 | 7 | /** 8 | * Turns an object into a string of xml attribute key-value pairs. 9 | * @function 10 | * @param {Object} attrs an object mapping attribute names to attribute values 11 | * @return {String} a string of xml attribute key-value pairs 12 | */ 13 | export function attrs(attrs) { 14 | return Object.keys(attrs) 15 | .map((a) => attrs[a] ? ` ${a}="${ escape(attrs[a]) }"` : '') 16 | .join(''); 17 | } 18 | 19 | /** 20 | * Creates a string xml tag. 21 | * @function 22 | * @param {String} ns the tag's xml namespace abbreviation. 23 | * @param {String} tagName the tag name. 24 | * @param {Object} attrsObj @see xml.attrs. 25 | * @param {String} inner inner xml. 26 | * @return {String} an xml string. 27 | */ 28 | export function tag(ns, tagName, attrsObj, inner) { 29 | let tag = (ns ? `${ns}:` : '') + tagName; 30 | if (tagName) { 31 | return `<${tag}${attrs(attrsObj)}${ 32 | inner !== null ? `>${inner}`; 34 | } else { 35 | throw new Error('no tag supplied ' + 36 | JSON.stringify({ns, tagName, attrsObj, inner}, null, 2) 37 | ); 38 | } 39 | }; 40 | 41 | /** 42 | * Shorthand for creating a wfs xml tag. 43 | * @param {String} tagName a valid wfs tag name. 44 | * @param {Object} attrsObj @see xml.attrs. 45 | * @param {String} inner @see xml.tag. 46 | * @return {String} a wfs element. 47 | */ 48 | export const wfs = (tagName, attrsObj, inner) => 49 | tag('wfs', tagName, attrsObj, inner); 50 | 51 | /** 52 | * Creates a fes:ResourceId filter from a layername and id 53 | * @function 54 | * @param {String} lyr layer name of the filtered feature 55 | * @param {String} id feature id 56 | * @return {String} a filter-ecoding of the filter. 57 | */ 58 | export const idFilter = (lyr, id) => { 59 | return ``; 60 | }; 61 | 62 | /** 63 | * Creates an xml-safe string from a given input string 64 | * @function 65 | * @param {String} input String to escape 66 | * @return {String} XML-safe string 67 | */ 68 | export function escape(input) { 69 | if (typeof input !== 'string') { 70 | // Backup check for non-strings 71 | return input; 72 | } 73 | 74 | const output = input.replace(/[<>&'"]/g, (char) => { 75 | switch (char) { 76 | case '<': 77 | return '<'; 78 | case '>': 79 | return '>'; 80 | case '&': 81 | return '&'; 82 | case `'`: 83 | return '''; 84 | case '"': 85 | return '"'; 86 | } 87 | }); 88 | 89 | return output; 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geojson-to-wfs-t-2 2 | [![npm version](https://badge.fury.io/js/geojson-to-wfs-t-2.svg)](https://badge.fury.io/js/geojson-to-wfs-t-2) 3 | [![Build Status](https://img.shields.io/travis/SKalt/geojson-to-wfs-t-2/master.svg)](https://travis-ci.org/SKalt/geojson-to-wfs-t-2) 4 | 5 | 6 | A library to create string Web Feature Service XML from geojson. As a string formatting library, `geojson-to-wfst-2` has only one dependency and will work in any environment. 7 | 8 | ## Installation 9 | get the library by executing 10 | ``` 11 | npm install geojson-to-wfs-t-2 12 | ``` 13 | or 14 | 15 | ``` 16 | git clone https://github.com/SKalt/geojson-to-wfs-t-2.git 17 | ``` 18 | 19 | and `import/require`-ing es6 or transpiled es5 commonjs, UMD, or es modules from `geojson-to-wfs-t-2/dist/`. 20 | 21 | ## Usage 22 | 23 | ```{javascript} 24 | import wfs from 'geojson-to-wfs-t-2'; 25 | 26 | const nullIsland = { 27 | type: 'Feature', 28 | properties: {place_name: 'null island'}, 29 | geometry: { 30 | type: 'Point', 31 | coordinates: [0, 0] 32 | } 33 | id: 'feature_id' 34 | } 35 | const params = {geometry_name: 'geom', layer: 'my_lyr', ns: 'my_namespace'}; 36 | 37 | // create a stringified transaction inserting null island 38 | wfs.Transaction( 39 | wfs.Insert(nullIsland, params), 40 | { 41 | nsAssignments: { 42 | my_namespace: 'https://example.com/namespace_defn.xsd' 43 | } 44 | } 45 | ); 46 | 47 | // create a stringified transaction updating null island's name 48 | wfs.Transaction( 49 | wfs.Update({properties: {place_name: 'not Atlantis'}, id: nullIsland.id }), 50 | {nsAssignments: ...} 51 | ) 52 | // same deal, but deleting it 53 | wfs.Transaction( 54 | wfs.Delete({id: nullIsland.id}, params), 55 | {nsAssignments: ...} 56 | ) 57 | ``` 58 | See [API.md](./API.md) for the full API documentation. 59 | 60 | #### Further notes: 61 | 62 | - While you should make sure to secure permissions to your data elsewhere (such as the [geoserver layer-level permissions](http://docs.geoserver.org/stable/en/user/security/layer.html)), you excluding or encapsulating dangerous actions like `Delete` is a good idea. 63 | 64 | - The functions `Insert, Update, Replace, Delete` in this module have remained uppercase (1) to remain similar to their xml names and (2) to avoid the keyword `delete`. If you would prefer to adhere to camelcase naming, consider aliasing the functions on import, like `import {Insert as insert} from '...'` 65 | 66 | ## Contributing 67 | 68 | Features, refactors, documentation, and tests are welcome! To contribute, branch or fork this repo, optionally open an issue, and then open a pull request. Please include tests 69 | and well-commented commits in all work. 70 | 71 | Here's an example script to start developing a feature on this project: 72 | ``` 73 | git clone https://github.com/SKalt/geojson-to-wfs-t-2.git # or your fork 74 | cd geojson-to-wfs-t-2 75 | git checkout -b informative-feature-branch 76 | npm install 77 | ``` 78 | -------------------------------------------------------------------------------- /src/typeDefs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An object containing optional named parameters. 3 | * @typedef {Object} Params 4 | * @prop {?string} ns an xml namespace alias. 5 | * @prop {?string|?Object} layer a string layer name or {id}, where id 6 | * is the layer name 7 | * @prop {?string} geometry_name the name of the feature geometry 8 | * field. 9 | * @prop {?Object} properties an object mapping feature field names to 10 | * feature properties 11 | * @prop {?string} id a string feature id. 12 | * @prop {?string[]} whitelist an array of string field names to 13 | * use from @see Params.properties 14 | * @prop {?string} inputFormat inputFormat, as specified at 15 | * [OGC 09-025r2 § 7.6.5.4]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#65}. 16 | * @prop {?string} srsName srsName, as specified at 17 | * [OGC 09-025r2 § 7.6.5.5]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#66}. 18 | * if undefined, the gml3 module will default to 'EPSG:4326'. 19 | * @prop {?string} handle handle parameter, as specified at 20 | * [OGC 09-025r2 § 7.6.2.6 ]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#44} 21 | * @prop {?string} filter a string fes:Filter. 22 | * @prop {?string} typeName a string specifying the feature type within 23 | * its namespace. See [09-025r2 § 7.9.2.4.1]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#90}. 24 | * @prop {?Object} schemaLocations an object mapping uri to 25 | * schema locations 26 | * @prop {?Object} nsAssignments an object mapping ns to uri 27 | */ 28 | 29 | /** 30 | * An object containing optional named parameters for a transaction in addition 31 | * to parameters used elsewhere. 32 | * @typedef {Object} TransactionParams 33 | * @extends Params 34 | * @prop {?string} lockId lockId parameter, as specified at 35 | * [OGC 09-025r2 § 15.2.3.1.2]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#277}. 36 | * @prop {?string} releaseAction releaseAction parameter, as specified 37 | * at [OGC 09-025r2 § 15.2.3.2]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#278}. 38 | */ 39 | 40 | /** 41 | * A GeoJSON feature with the following optional foreign members (see 42 | * [rfc7965 § 6]{@link https://tools.ietf.org/html/rfc7946#section-6}). 43 | * or an object with some of the following members. 44 | * Members of Feature will be used over those in Params except for layer, id, 45 | * and properties. 46 | * @typedef {Object} Feature 47 | * @extends Params 48 | * @property {?Object} geometry a GeoJSON geometry. 49 | * @property {?string} type 'Feature'. 50 | * @example 51 | * {'id':'tasmania_roads.1', 'typeName':'topp:tasmania_roadsType'} 52 | * // can be passed to Delete 53 | */ 54 | 55 | /** 56 | * a GeoJSON FeatureCollection with optional foreign members as in Feature. 57 | * @typedef {Object} FeatureCollection 58 | * @extends Feature 59 | * @property {string} type 'FeatureCollection'. 60 | * @property {Feature[]} features an array of GeoJSON Features. 61 | */ 62 | -------------------------------------------------------------------------------- /validation/featureExamples.js: -------------------------------------------------------------------------------- 1 | 2 | const makeId = ( 3 | ()=>{ 4 | let counter = 15; 5 | return () => { 6 | counter += 1; 7 | return `tasmania_roads.${counter}`; 8 | }; 9 | } 10 | )(); 11 | 12 | const basicGeojson = { // complete feature 13 | "type":"Feature", 14 | "id":"tasmania_roads.15", 15 | "geometry":{ 16 | "type":"MultiLineString", 17 | "coordinates":[ 18 | [ 19 | [146.4685,-41.241478], 20 | [146.5747,-41.251186], 21 | [146.6404,-41.255154], 22 | [146.7661,-41.332348], 23 | ] 24 | ] 25 | }, 26 | "geometry_name":"the_geom", 27 | "properties":{ 28 | "TYPE":"RnbwRd" 29 | }, 30 | "ns":"topp", 31 | "layer":{ 32 | "id":"tasmania_roads" 33 | }, 34 | "srsName":"EPSG:4326" 35 | }; 36 | 37 | const updateErrorGeojson = { 38 | "type": "Feature", 39 | "geometry": { 40 | "type": "Polygon", 41 | "coordinates": [ 42 | [ 43 | [3550776.6, 5810513.2], 44 | [3550771, 5810342.4], 45 | [3551053.8, 5810325.6], 46 | [3551079, 5810499.2], 47 | [3550776.6, 5810513.2] 48 | ] 49 | ] 50 | }, 51 | "crs": { 52 | "type": "name", 53 | "properties": { 54 | "name": "EPSG:31467" 55 | } 56 | } 57 | }; 58 | 59 | const makeFeature = (overrides, ...deletions) =>{ 60 | let newFeature = Object.assign({}, basicGeojson, overrides); 61 | 62 | deletions.forEach((d)=> delete newFeature[d]); 63 | return newFeature; 64 | }; 65 | 66 | const should_work_inputs = { //feature/s, params pairs 67 | // empty params, feature-only tests 68 | // insertions 69 | "complete feature, empty params":[basicGeojson, {}], 70 | "complete feature, undefined params":[makeFeature('', 'geometry_name')], 71 | "separated layer, id number":[ 72 | makeFeature({id:"15", layer:"tasmania_roads"}, 'geometry_name'), {} 73 | ], 74 | "layer override":[makeFeature('', 'geometry_name'), {"layer":"tasmania_roads"}], 75 | "parameter override":[ 76 | makeFeature({"srsName":"EPSG:3857"}, 'geometry_name'), 77 | {"srsName":"EPSG:4326"} 78 | ], 79 | // TODO: add case sensitive attr checkers. 80 | "feature array":[[ 81 | makeFeature({id:makeId()}, 'geometry_name'), 82 | makeFeature({id:makeId()}, 'geometry_name') 83 | ], {}], 84 | "featureCollection": [ 85 | {"type":"FeatureCollection", 86 | "features":[ 87 | makeFeature({id:makeId()}, 'geometry_name'), 88 | makeFeature({id:makeId()}, 'geometry_name') 89 | ] 90 | }, {}], 91 | "whitelist":[ 92 | makeFeature({ 93 | "properties":{ 94 | "TYPE":"FuryRd", 95 | "Irrelevant":"blah" 96 | } 97 | }, 'geometry_name'), 98 | { 99 | "whitelist":["TYPE"] 100 | } 101 | ], 102 | "update error feature": [ 103 | makeFeature(updateErrorGeojson, 'geometry_name'), { 104 | "srsName": "http://www.opengis.net/def/crs/EPSG/0/31467" 105 | }] 106 | }; 107 | 108 | module.exports = {testCases:should_work_inputs, feature:basicGeojson}; 109 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import {geomToGml as gml3} from 'geojson-to-gml-3/src/index.js'; 2 | import {tag as xmlTag, escape as xmlEscape} from './xml.js'; 3 | import {id as ensureId} from './ensure.js'; 4 | 5 | /* eslint-disable camelcase */ 6 | /** 7 | * Common utilities for handling parameters for creation of WFS trasactions. 8 | * @module utils 9 | */ 10 | 11 | /** 12 | * Iterates over the key-value pairs, filtering by a whitelist if available. 13 | * @function 14 | * @param {Array} whitelist a whitelist of property names 15 | * @param {Object} properties an object mapping property names to values 16 | * @param {Function} cb a function to call on each (whitelisted key, value) pair 17 | */ 18 | export const useWhitelistIfAvailable = (whitelist, properties, cb) =>{ 19 | for (let prop of whitelist || Object.keys(properties)) { 20 | const val = properties[prop]; 21 | if (Number.isNaN(val)) { 22 | throw new Error('NaN is not allowed.'); 23 | } 24 | if (val !== undefined) { 25 | cb(prop, val); 26 | } 27 | } 28 | }; 29 | 30 | const featureMembers = new Set(['properties', 'geometry', 'id', 'layer']); 31 | /** 32 | * Resolves attributes from feature, then params unless they are normally 33 | * found in the feature 34 | * @param {Object} feature a geojson feature 35 | * @param {Object} params an object of backup / override parameters 36 | * @param {Array} args parameter names to resolve from feature or 37 | * params 38 | * @return {Object} an object mapping each named parameter to its resolved 39 | * value 40 | */ 41 | export function unpack(feature, params, ...args) { 42 | let results = {}; 43 | for (let arg of args) { 44 | if (arg === 'layer') { 45 | results[arg] = (params.layer || {}).id 46 | || params.layer 47 | || (feature.layer||{}).id 48 | || feature.layer 49 | || ''; 50 | } else if (!featureMembers.has(arg)) { 51 | results[arg] = feature[arg] 52 | || params[arg] 53 | || ''; 54 | } else { 55 | results[arg] = params[arg] 56 | || feature[arg] 57 | || ''; 58 | } 59 | } 60 | return results; 61 | }; 62 | 63 | /** 64 | * Generates an object to be passed to @see xml.attrs xmlns:ns="uri" definitions 65 | * for a wfs:Transaction 66 | * @param {Object} nsAssignments @see Params.nsAssignments 67 | * @param {String} xml arbitrary xml. 68 | * @return {Object} an object mapping each ns to its URI as 'xmlns:ns' : 'URI'. 69 | * @throws {Error} if any namespace used within `xml` is missing a URI 70 | * definition 71 | */ 72 | export function generateNsAssignments(nsAssignments, xml) { 73 | let attrs = {}; 74 | const makeNsAssignment = (ns, uri) => attrs[`xmlns:${ns}`] = uri; 75 | Object.keys(nsAssignments).forEach((ns) => { 76 | makeNsAssignment(ns, nsAssignments[ns]); 77 | }); 78 | // check all ns's assigned 79 | let re = /(<|typeName=")(\w+):/g; 80 | let arr; 81 | let allNamespaces = new Set(); 82 | while ((arr = re.exec(xml)) !== null) { 83 | allNamespaces.add(arr[2]); 84 | } 85 | if (allNamespaces.has('fes')) { 86 | makeNsAssignment('fes', 'http://www.opengis.net/fes/2.0'); 87 | }; 88 | makeNsAssignment('xsi', 'http://www.w3.org/2001/XMLSchema-instance'); 89 | makeNsAssignment('gml', 'http://www.opengis.net/gml/3.2'); 90 | makeNsAssignment('wfs', 'http://www.opengis.net/wfs/2.0'); 91 | 92 | for (let ns of allNamespaces) { 93 | if (!attrs['xmlns:' + ns]) { 94 | throw new Error(`unassigned namespace ${ns}`); 95 | } 96 | }/* , schemaLocations*/ 97 | return attrs; 98 | } 99 | 100 | /** 101 | * Returns a string alternating uri, whitespace, and the uri's schema's 102 | * location. 103 | * @param {Object} schemaLocations an object mapping uri:schemalocation 104 | * @return {string} a string that is a valid xsi:schemaLocation value. 105 | */ 106 | export function generateSchemaLines(schemaLocations={}) { 107 | // TODO: add ns assignment check 108 | schemaLocations['http://www.opengis.net/wfs/2.0'] = 109 | 'http://schemas.opengis.net/wfs/2.0/wfs.xsd'; 110 | let schemaLines = []; 111 | Object.entries(schemaLocations).forEach( 112 | (entry)=>schemaLines.push(entry.join('\n')) 113 | ); 114 | return schemaLines.join('\n'); 115 | } 116 | 117 | /** 118 | * Turns an array of geojson features into gml:_feature strings describing them. 119 | * @function 120 | * @param {Feature[]} features an array of features to translate to 121 | * gml:_features. 122 | * @param {Params} params an object of backup / override parameters 123 | * @return {String} a gml:_feature string. 124 | */ 125 | export function translateFeatures(features, params={}) { 126 | let inner = ''; 127 | let {srsName, srsDimension} = params; 128 | for (let feature of features) { 129 | // TODO: add whitelist support 130 | let {ns, layer, geometry_name, properties, id, whitelist} = unpack( 131 | feature, params, 'ns', 'layer', 'geometry_name', 'properties', 'id', 132 | 'whitelist' 133 | ); 134 | let fields = ''; 135 | if (geometry_name) { 136 | fields += xmlTag( 137 | ns, geometry_name, {}, 138 | gml3(feature.geometry, '', {srsName, srsDimension}) 139 | ); 140 | } 141 | useWhitelistIfAvailable( 142 | whitelist, properties, 143 | (prop, val)=> { 144 | if (val === null) { 145 | return fields; 146 | } 147 | return fields += xmlTag(ns, prop, {}, xmlEscape(properties[prop])); 148 | } 149 | ); 150 | inner += xmlTag(ns, layer, {'gml:id': ensureId(layer, id)}, fields); 151 | } 152 | return inner; 153 | } 154 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase, new-cap */ 2 | // some snake_case variables are used to imitate gml's notation. 3 | /** 4 | * A library of functions to turn geojson into WFS transactions. 5 | * @module geojsonToWfst 6 | */ 7 | import {geomToGml as gml3} from 'geojson-to-gml-3/src/index.js'; 8 | import { 9 | array as ensureArray, 10 | // id as ensureId, 11 | typeName as ensureTypeName 12 | } from './ensure.js'; 13 | import {filter as ensureFilter} from './filter.js'; 14 | import {tag as xmlTag, escape as xmlEscape, wfs} from './xml.js'; 15 | import { 16 | generateNsAssignments, translateFeatures, useWhitelistIfAvailable, unpack, 17 | generateSchemaLines 18 | } from './utils.js'; 19 | 20 | /** 21 | * Returns a wfs:Insert tag wrapping a translated feature 22 | * @function 23 | * @param {Feature[]|FeatureCollection|Feature} features Feature(s) to pass to 24 | * @see translateFeatures 25 | * @param {Params} params to be passed to @see translateFeatures, with optional 26 | * inputFormat, srsName, handle for the wfs:Insert tag. 27 | * @return {string} a wfs:Insert string. 28 | */ 29 | export function Insert(features, params={}) { 30 | features = ensureArray(features); 31 | let {inputFormat, srsName, handle} = params; 32 | if (!features.length) { 33 | console.warn('no features supplied'); 34 | return ''; 35 | } 36 | let toInsert = translateFeatures(features, params); 37 | return xmlTag('wfs', 'Insert', {inputFormat, srsName, handle}, toInsert); 38 | } 39 | 40 | /** 41 | * Updates the input features in bulk with params.properties or by id. 42 | * @param {Feature[]|FeatureCollection} features features to update. These may 43 | * pass in geometry_name, properties, and layer (overruled by params) and 44 | * ns, layer, srsName (overruling params). 45 | * @param {Params} params with optional properties, ns (namespace), layer, 46 | * geometry_name, filter, typeName, whitelist. 47 | * @return {string} a string wfs:Upate action. 48 | */ 49 | export function Update(features, params={}) { 50 | features = ensureArray(features); 51 | /** 52 | * makes a wfs:Property string containg a wfs:ValueReference, wfs:Value pair. 53 | * @private 54 | * @function 55 | * @memberof Update~ 56 | * @param {string} prop the field/property name 57 | * @param {string} val the field/property value 58 | * @param {string} action one of 'insertBefore', 'insertAfter', 'remove', 59 | * 'replace'. See [OGC 09-025r2 § 15.2.5.2.1]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286}. 60 | * `action` would delete or modify the order of fields within the remote 61 | * feature. There is currently no way to input `action,` since wfs:Update's 62 | * default action, 'replace', is sufficient. 63 | * @return {string} a wfs:Property(wfs:ValueReference) pair. 64 | */ 65 | const makeKvp = (prop, val, action) => { 66 | let value = ''; 67 | if (val === null) { 68 | value = wfs('Value', {'xsi:nil': true}, ''); 69 | } else if (val !== undefined) { 70 | value = wfs('Value', {}, val); 71 | } 72 | 73 | return wfs('Property', {}, wfs('ValueReference', {action}, prop) + value); 74 | }; 75 | 76 | if (params.properties) { 77 | let {/* handle, */inputFormat, filter, typeName, whitelist} = params; 78 | let {srsName, ns, layer, geometry_name} = unpack( 79 | features[0] || {}, params, 'srsName', 'ns', 'layer', 'geometry_name'); 80 | typeName = ensureTypeName(ns, layer, typeName); 81 | filter = ensureFilter(filter, features, params); 82 | if (!filter && !features.length) { 83 | console.warn('neither features nor filter supplied'); 84 | return ''; 85 | } 86 | let fields = ''; 87 | useWhitelistIfAvailable( // TODO: action attr 88 | whitelist, params.properties, (k, v) => 89 | fields += makeKvp(k, xmlEscape(v)) 90 | ); 91 | if (geometry_name) { 92 | fields += makeKvp( 93 | geometry_name, xmlTag( 94 | ns, geometry_name, {}, gml3(params.geometry, '', {srsName}) 95 | ) 96 | ); 97 | } 98 | return wfs('Update', {inputFormat, srsName, typeName}, fields + filter); 99 | } else { 100 | // encapsulate each update in its own Update tag 101 | return features.map( 102 | (f) => Update( 103 | f, Object.assign({}, params, {properties: f.properties}) 104 | ) 105 | ).join(''); 106 | } 107 | } 108 | 109 | /** 110 | * Creates a wfs:Delete action, creating a filter and typeName from feature ids 111 | * if none are supplied. 112 | * @param {Feature[]|FeatureCollection|Feature} features 113 | * @param {Params} params optional parameter overrides. 114 | * @param {string} [params.ns] @see Params.ns 115 | * @param {string|Object} [params.layer] @see Params.layer 116 | * @param {string} [params.typeName] @see Params.typeName. This will be inferred 117 | * from feature/params layer and ns if this is left undefined. 118 | * @param {filter} [params.filter] @see Params.filter. This will be inferred 119 | * from feature ids and layer(s) if left undefined (@see ensureFilter). 120 | * @return {string} a wfs:Delete string. 121 | */ 122 | export function Delete(features, params={}) { 123 | features = ensureArray(features); 124 | let {filter, typeName} = params; // TODO: recur & encapsulate by typeName 125 | let {ns, layer} = unpack(features[0] || {}, params, 'layer', 'ns'); 126 | typeName = ensureTypeName(ns, layer, typeName); 127 | filter = ensureFilter(filter, features, params); 128 | return wfs('Delete', {typeName}, filter); 129 | } 130 | 131 | /** 132 | * Returns a string wfs:Replace action. 133 | * @param {Feature[]|FeatureCollection|Feature} features feature(s) to replace 134 | * @param {Params} params with optional filter, inputFormat, srsName 135 | * @return {string} a string wfs:Replace action. 136 | */ 137 | export function Replace(features, params={}) { 138 | features = ensureArray(features); 139 | let {filter, inputFormat, srsName} = unpack( 140 | features[0] || {}, params || {}, 'filter', 'inputFormat', 'srsName' 141 | ); 142 | let replacements = translateFeatures( 143 | [features[0]].filter((f)=>f), 144 | params || {srsName} 145 | ); 146 | filter = ensureFilter(filter, features, params); 147 | return wfs('Replace', {inputFormat, srsName}, replacements + filter); 148 | } 149 | 150 | /** 151 | * Wraps the input actions in a wfs:Transaction. 152 | * @param {Object|string[]|string} actions an object mapping {Insert, Update, 153 | * Delete} to feature(s) to pass to Insert, Update, Delete, or wfs:action 154 | * string(s) to wrap in a transaction. 155 | * @param {TransactionParams} params optional srsName, lockId, releaseAction, 156 | * handle, inputFormat, version, and required nsAssignments, schemaLocations. 157 | * @return {string} A wfs:transaction wrapping the input actions. 158 | * @throws {Error} if `actions` is not an array of strings, a string, or 159 | * {@see Insert, @see Update, @see Delete}, where each action are valid inputs 160 | * to the eponymous function. 161 | */ 162 | export function Transaction(actions, params={}) { 163 | const transactionParams = ['srsName', 'lockId', 'releaseAction', 'handle']; 164 | let { 165 | version, // optional 166 | nsAssignments = {} // required 167 | } = params; 168 | // let converter = {Insert, Update, Delete}; 169 | let {insert: toInsert, update: toUpdate, delete: toDelete} = actions || {}; 170 | let finalActions = ''; // processedActions would be more accurate 171 | 172 | if (Array.isArray(actions) && actions.every((v) => typeof(v) == 'string')) { 173 | finalActions += actions.join(''); 174 | } else if (typeof(actions) == 'string') { 175 | finalActions = actions; 176 | } else if ([toInsert, toUpdate, toDelete].some((e) => e)) { 177 | finalActions += Insert(toInsert, params) + 178 | Update(toUpdate, params) + 179 | Delete(toDelete, params); 180 | } else { 181 | throw new Error(`unexpected input: ${JSON.stringify(actions)}`); 182 | } 183 | // generate schemaLocation, xmlns's 184 | let attrs = generateNsAssignments(nsAssignments, actions); 185 | attrs['xsi:schemaLocation'] = generateSchemaLines(params.schemaLocations); 186 | attrs['service'] = 'WFS'; 187 | attrs['version'] = /2\.0\.\d+/.exec(version || '') ? version : '2.0.0'; 188 | transactionParams.forEach((param) => { 189 | if (params[param]) { 190 | attrs[param] = params[param]; 191 | } 192 | }); 193 | return wfs('Transaction', attrs, finalActions); 194 | } 195 | -------------------------------------------------------------------------------- /validation/test.js: -------------------------------------------------------------------------------- 1 | /* eslint new-cap: 0 max-len: 0 */ 2 | const validate = require('xsd-schema-validator').validateXML; 3 | const formatXml = require('./formatXml.js'); 4 | // const wfs = require('../geojsonToWfst.js'); 5 | const wfs = require('../dist/es6.cjs.js'); 6 | const {testCases, feature} = require('./featureExamples.js'); // in separate 7 | // module since the fixtures are many lines of code. 8 | const assert = require('assert'); 9 | 10 | const isValidWfst = (xml) => { 11 | return new Promise(function(res, rej) { 12 | validate(xml, './fixtures/mock_schema.xsd', (err, result) => { 13 | if (err) rej(err); 14 | res(result); 15 | }); 16 | }).catch((err)=>{ 17 | console.log(formatXml(xml)); 18 | throw err; 19 | }); 20 | }; 21 | const tempId = (()=>{ 22 | let counter = 1; 23 | return ()=>{ 24 | counter += 1; 25 | return ` gml:id="ab.${counter}"`; 26 | }; 27 | })(); 28 | 29 | let test = (testCaseId, action, note) =>{ // TODO: anti-tests. 30 | it(`${testCaseId} : ${action}; ${note || ''}`, 31 | function() { 32 | const [feature, params] = testCases[testCaseId]; 33 | let xml = wfs.Transaction( 34 | wfs[action](feature, params), 35 | { 36 | nsAssignments: { 37 | topp: 'http://www.openplans.org/topp' 38 | }, 39 | schemaLocations: {} 40 | } 41 | ); 42 | xml = xml.replace( 43 | /`${match + tempId()}` 45 | ); 46 | return isValidWfst(xml); 47 | } 48 | ); 49 | }; 50 | const tests = (testCaseId, ...actions) => actions.forEach( 51 | (action) => test(testCaseId, action) 52 | ); 53 | 54 | // [ 'complete feature, empty params', 55 | // 'complete feature, undefined params', 56 | // 'separated layer, id number', 57 | // 'layer override', 58 | // 'parameter override', 59 | // 'feature array', 60 | // 'featureCollection', 61 | // 'whitelist' ] 62 | describe('Generation of valid WFS-T-2.0.0', function() { 63 | // it('exists / is visible', function(){ 64 | // console.log(formatXml(wfs.Insert(feature)), '\n-------------------'); 65 | // console.log(formatXml(wfs.Update(feature)), '\n-------------------'); 66 | // console.log( 67 | // formatXml(wfs.Delete(feature, {ns:'topp'})), '\n-------------------'); 68 | // console.log(formatXml(wfs.Transaction(wfs.Delete(feature), { 69 | // nsAssignments:{ 70 | // topp:'http://www.openplans.org/topp' 71 | // }, 72 | // schemaLocations:{} 73 | // })), '\n-------------------'); 74 | // }); 75 | // for (let testCase in testCases){ 76 | // let testCase = 'complete feature, empty params'; 77 | tests('complete feature, empty params', 78 | 'Insert', 'Replace', 'Delete'); 79 | tests('complete feature, undefined params', 80 | 'Insert', 'Replace', 'Update', 'Delete'); 81 | tests('separated layer, id number', 82 | 'Insert', 'Replace', 'Update', 'Delete'); 83 | tests('layer override', 84 | 'Insert', 'Replace', 'Update', 'Delete'); 85 | tests('parameter override', 86 | 'Insert', 'Replace', 'Update', 'Delete'); 87 | tests('feature array', 88 | 'Insert', 'Replace', 'Update', 'Delete'); 89 | tests('featureCollection', 90 | 'Insert', 'Replace', 'Update', 'Delete'); 91 | tests('whitelist', 92 | 'Insert', 'Update', 'Replace'); 93 | tests('update error feature', 94 | 'Insert', 'Update', 'Replace', 'Delete'); 95 | }); 96 | test = (testCaseId, action, note) => { // TODO: anti-tests. 97 | it(`${testCaseId} : ${action}; ${note || ''}`, 98 | function() { 99 | const [feature, params] = testCases[testCaseId]; 100 | let xml = wfs.Transaction( 101 | wfs[action](feature, params), 102 | { 103 | nsAssignments: { 104 | topp: 'http://www.openplans.org/topp' 105 | }, 106 | schemaLocations: {} 107 | } 108 | ); 109 | xml = xml.replace( 110 | /`${match + tempId()}` 112 | ); 113 | return isValidWfst(xml) 114 | .catch(()=>true) 115 | .then(()=>{ 116 | throw new Error('should have thrown an error'); 117 | }); 118 | } 119 | ); 120 | }; 121 | describe('Appropriate throwing of errors', function() {}); 122 | 123 | describe('Handles falsy values correctly.', () => { 124 | describe('empty string value', () => { 125 | it('Insert', () => { 126 | const testFeature = Object.assign({}, feature); 127 | testFeature.properties = Object.assign({}, feature.properties, { 128 | emptystring: '' 129 | }); 130 | 131 | const insert = wfs.Insert(testFeature); 132 | const xml = wfs.Transaction([insert], { 133 | nsAssignments: { 134 | topp: 'http://www.openplans.org/topp' 135 | } 136 | }); 137 | 138 | const match = xml.match(/<\/topp:emptystring>/); 139 | 140 | assert.notEqual( 141 | match, null, 142 | 'An xml match must be found for emptystring'); 143 | }); 144 | 145 | it('Update', () => { 146 | const testFeature = Object.assign( 147 | {}, feature, {geometry_name: undefined} 148 | ); 149 | 150 | const update = wfs.Update(testFeature, { 151 | properties: { 152 | emptystring: '' 153 | } 154 | }); 155 | const xml = wfs.Transaction([update], { 156 | nsAssignments: { 157 | topp: 'http://www.openplans.org/topp' 158 | } 159 | }); 160 | const match = xml.match(/emptystring<\/wfs:ValueReference><\/wfs:Value>/); 161 | 162 | assert.notEqual(match, null, 'An xml match must be found for emptystring'); 163 | }); 164 | }); 165 | 166 | describe('false value', () => { 167 | it('Insert', () => { 168 | const testFeature = Object.assign({}, feature); 169 | testFeature.properties = Object.assign({}, feature.properties, { 170 | falsevalue: false 171 | }); 172 | 173 | const insert = wfs.Insert(testFeature); 174 | const xml = wfs.Transaction([insert], { 175 | nsAssignments: { 176 | topp: 'http://www.openplans.org/topp' 177 | } 178 | }); 179 | 180 | const match = xml.match(/false<\/topp:falsevalue>/); 181 | 182 | assert.notEqual(match, null, 'An xml match must be found for falsevalue'); 183 | }); 184 | 185 | it('Update', () => { 186 | const testFeature = Object.assign({}, feature, {geometry_name: undefined}); 187 | 188 | const update = wfs.Update(testFeature, { 189 | properties: { 190 | falsevalue: false 191 | } 192 | }); 193 | const xml = wfs.Transaction([update], { 194 | nsAssignments: { 195 | topp: 'http://www.openplans.org/topp' 196 | } 197 | }); 198 | 199 | const match = xml.match(/falsevalue<\/wfs:ValueReference>false<\/wfs:Value>/); 200 | 201 | assert.notEqual(match, null, 'An xml match must be found for emptystring'); 202 | }); 203 | }); 204 | 205 | describe('0 value', () => { 206 | it('Insert', () => { 207 | const testFeature = Object.assign({}, feature); 208 | testFeature.properties = Object.assign({}, feature.properties, { 209 | zero: 0 210 | }); 211 | 212 | const insert = wfs.Insert(testFeature); 213 | const xml = wfs.Transaction([insert], { 214 | nsAssignments: { 215 | topp: 'http://www.openplans.org/topp' 216 | } 217 | }); 218 | 219 | const match = xml.match(/0<\/topp:zero>/); 220 | 221 | assert.notEqual(match, null, 'An xml match must be found for zero'); 222 | }); 223 | 224 | it('Update', () => { 225 | const testFeature = Object.assign({}, feature, {geometry_name: undefined}); 226 | 227 | const update = wfs.Update(testFeature, { 228 | properties: { 229 | zero: 0 230 | } 231 | }); 232 | const xml = wfs.Transaction([update], { 233 | nsAssignments: { 234 | topp: 'http://www.openplans.org/topp' 235 | } 236 | }); 237 | 238 | const match = xml.match(/zero<\/wfs:ValueReference>0<\/wfs:Value>/); 239 | 240 | assert.notEqual(match, null, 'An xml match must be found for zero'); 241 | }); 242 | }); 243 | 244 | describe('null value', () => { 245 | it('Insert', () => { 246 | const testFeature = Object.assign({}, feature); 247 | testFeature.properties = Object.assign({}, feature.properties, { 248 | nullvalue: null 249 | }); 250 | 251 | const insert = wfs.Insert(testFeature); 252 | const xml = wfs.Transaction([insert], { 253 | nsAssignments: { 254 | topp: 'http://www.openplans.org/topp' 255 | } 256 | }); 257 | 258 | const match = xml.match(/nullvalue/); 259 | 260 | assert.equal(match, null, 'Null values should not appear in an Insert'); 261 | }); 262 | 263 | it('Update', () => { 264 | const testFeature = Object.assign({}, feature, {geometry_name: undefined}); 265 | 266 | const update = wfs.Update(testFeature, { 267 | properties: { 268 | nullvalue: null 269 | } 270 | }); 271 | const xml = wfs.Transaction([update], { 272 | nsAssignments: { 273 | topp: 'http://www.openplans.org/topp' 274 | } 275 | }); 276 | 277 | const match = xml.match(/nullvalue<\/wfs:ValueReference><\/wfs:Value>/); 278 | 279 | assert.notEqual(match, null, 'An xml match must be found for nullvalue'); 280 | }); 281 | }); 282 | 283 | describe('undefined value', () => { 284 | it('Insert', () => { 285 | const testFeature = Object.assign({}, feature); 286 | testFeature.properties = Object.assign({}, feature.properties, { 287 | undefinedvalue: undefined 288 | }); 289 | 290 | const insert = wfs.Insert(testFeature); 291 | const xml = wfs.Transaction([insert], { 292 | nsAssignments: { 293 | topp: 'http://www.openplans.org/topp' 294 | } 295 | }); 296 | 297 | const match = xml.match(/undefinedvalue/); 298 | 299 | assert.equal(match, null, 'Undefined values should not appear in an Insert'); 300 | }); 301 | 302 | it('Update', () => { 303 | const testFeature = Object.assign({}, feature, {geometry_name: undefined}); 304 | 305 | const update = wfs.Update(testFeature, { 306 | properties: { 307 | undefinedvalue: undefined 308 | } 309 | }); 310 | const xml = wfs.Transaction([update], { 311 | nsAssignments: { 312 | topp: 'http://www.openplans.org/topp' 313 | } 314 | }); 315 | 316 | const match = xml.match(/undefinedvalue/); 317 | 318 | assert.equal( 319 | match, null, 'Undefined values should not appear in an Update' 320 | ); 321 | }); 322 | }); 323 | 324 | describe('NaN value', () => { 325 | it('Insert', () => { 326 | const testFeature = Object.assign({}, feature); 327 | testFeature.properties = Object.assign({}, feature.properties, { 328 | nanvalue: NaN 329 | }); 330 | 331 | assert.throws(() => wfs.Insert(testFeature), 332 | /NaN is not allowed/, 333 | 'NaN in an Insert should throw.'); 334 | }); 335 | 336 | it('Update', () => { 337 | const testFeature = Object.assign( 338 | {}, feature, {geometry_name: undefined} 339 | ); 340 | 341 | assert.throws(() => wfs.Update(testFeature, { 342 | properties: { 343 | nanvalue: NaN 344 | } 345 | }), 346 | /NaN is not allowed/, 347 | 'NaN in an Update should throw.'); 348 | }); 349 | }); 350 | }); 351 | 352 | describe('Properly escapes XML special characters.', () => { 353 | it('Escapes characters in content.', () => { 354 | const testFeature = Object.assign({}, feature); 355 | testFeature.properties = Object.assign({}, feature.properties, { 356 | xmlescape: `Test < xml > & quotes " and apostrophes ' are escaped` 357 | }); 358 | 359 | const insert = wfs.Insert(testFeature); 360 | const xml = wfs.Transaction([insert], { 361 | nsAssignments: { 362 | topp: 'http://www.openplans.org/topp' 363 | } 364 | }); 365 | 366 | const match = xml.match(/Test < xml > & quotes " and apostrophes ' are escaped<\/topp:xmlescape>/); 367 | 368 | assert.notEqual( 369 | match, null, 370 | 'An xml match must be found for xmlescape'); 371 | }); 372 | 373 | it('Escapes characters in attributes.', () => { 374 | const testFeature = Object.assign({}, feature); 375 | testFeature.properties = Object.assign({}, feature.properties); 376 | 377 | const insert = wfs.Insert(testFeature, {inputFormat: `&<>"'`}); 378 | const xml = wfs.Transaction([insert], { 379 | nsAssignments: { 380 | topp: 'http://www.openplans.org/topp' 381 | } 382 | }); 383 | 384 | const match = xml.match(/ inputFormat="&<>"'"/); 385 | 386 | assert.notEqual( 387 | match, null, 388 | 'An xml match must be found for xmlescape'); 389 | }); 390 | 391 | it('Escapes characters in Update', () => { 392 | const testFeature = Object.assign({}, feature, {geometry_name: undefined}); 393 | 394 | const update = wfs.Update(testFeature, { 395 | properties: { 396 | xmlescape: `Test < xml > & quotes " and apostrophes ' are escaped` 397 | } 398 | }); 399 | const xml = wfs.Transaction([update], { 400 | nsAssignments: { 401 | topp: 'http://www.openplans.org/topp' 402 | } 403 | }); 404 | 405 | const match = xml.match(/xmlescape<\/wfs:ValueReference>Test < xml > & quotes " and apostrophes ' are escaped<\/wfs:Value>/); 406 | 407 | assert.notEqual( 408 | match, null, 'An xml match must be found for xmlescape' 409 | ); 410 | }); 411 | }); 412 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | ## Modules 2 | 3 |
4 |
ensure
5 |

common checks, coersions, and informative errors/ warnings

6 |
7 |
geojsonToWfst
8 |

A library of functions to turn geojson into WFS transactions.

9 |
10 |
utils
11 |

Common utilities for handling parameters for creation of WFS trasactions.

12 |
13 |
xml
14 |

xml utilities.

15 |
16 |
17 | 18 | ## Functions 19 | 20 |
21 |
filter(filter, features, params)String
22 |

Builds a filter from feature ids if one is not already input.

23 |
24 |
25 | 26 | ## Typedefs 27 | 28 |
29 |
Params : Object
30 |

An object containing optional named parameters.

31 |
32 |
TransactionParams : Object
33 |

An object containing optional named parameters for a transaction in addition 34 | to parameters used elsewhere.

35 |
36 |
Feature : Object
37 |

A GeoJSON feature with the following optional foreign members (see 38 | rfc7965 § 6). 39 | or an object with some of the following members. 40 | Members of Feature will be used over those in Params except for layer, id, 41 | and properties.

42 |
43 |
FeatureCollection : Object
44 |

a GeoJSON FeatureCollection with optional foreign members as in Feature.

45 |
46 |
47 | 48 | 49 | 50 | ## ensure 51 | common checks, coersions, and informative errors/ warnings 52 | 53 | 54 | * [ensure](#module_ensure) 55 | * _static_ 56 | * [.id(lyr, id)](#module_ensure.id) ⇒ String 57 | * [.typeName(ns, layer, typeName)](#module_ensure.typeName) ⇒ String 58 | * _inner_ 59 | * [~allowedActions(action)](#module_ensure..allowedActions) ⇒ Boolean 60 | 61 | 62 | 63 | ### ensure.id(lyr, id) ⇒ String 64 | Ensures a layer.id format of an input id. 65 | 66 | **Kind**: static method of [ensure](#module_ensure) 67 | **Returns**: String - a correctly-formatted gml:id 68 | 69 | | Param | Type | Description | 70 | | --- | --- | --- | 71 | | lyr | String | layer name | 72 | | id | String | id, possibly already in correct layer.id format. | 73 | 74 | 75 | 76 | ### ensure.typeName(ns, layer, typeName) ⇒ String 77 | return a correctly-formatted typeName 78 | 79 | **Kind**: static method of [ensure](#module_ensure) 80 | **Returns**: String - a correctly-formatted typeName 81 | **Throws**: 82 | 83 | - Error if typeName it cannot form a typeName from ns and layer 84 | 85 | 86 | | Param | Type | Description | 87 | | --- | --- | --- | 88 | | ns | String | namespace | 89 | | layer | String | layer name | 90 | | typeName | String | typeName to check | 91 | 92 | 93 | 94 | ### ensure~allowedActions(action) ⇒ Boolean 95 | Checks the type of the input action 96 | 97 | **Kind**: inner method of [ensure](#module_ensure) 98 | **Returns**: Boolean - whether the action is allowed 99 | 100 | | Param | Type | 101 | | --- | --- | 102 | | action | String | 103 | 104 | 105 | 106 | ## geojsonToWfst 107 | A library of functions to turn geojson into WFS transactions. 108 | 109 | 110 | * [geojsonToWfst](#module_geojsonToWfst) 111 | * [.Insert(features, params)](#module_geojsonToWfst.Insert) ⇒ string 112 | * [.Update(features, params)](#module_geojsonToWfst.Update) ⇒ string 113 | * [.Delete(features, params)](#module_geojsonToWfst.Delete) ⇒ string 114 | * [.Replace(features, params)](#module_geojsonToWfst.Replace) ⇒ string 115 | * [.Transaction(actions, params)](#module_geojsonToWfst.Transaction) ⇒ string 116 | 117 | 118 | 119 | ### geojsonToWfst.Insert(features, params) ⇒ string 120 | Returns a wfs:Insert tag wrapping a translated feature 121 | 122 | **Kind**: static method of [geojsonToWfst](#module_geojsonToWfst) 123 | **Returns**: string - a wfs:Insert string. 124 | **See**: translateFeatures 125 | 126 | | Param | Type | Description | 127 | | --- | --- | --- | 128 | | features | [Array.<Feature>](#Feature) \| [FeatureCollection](#FeatureCollection) \| [Feature](#Feature) | Feature(s) to pass to | 129 | | params | [Params](#Params) | to be passed to @see translateFeatures, with optional inputFormat, srsName, handle for the wfs:Insert tag. | 130 | 131 | 132 | 133 | ### geojsonToWfst.Update(features, params) ⇒ string 134 | Updates the input features in bulk with params.properties or by id. 135 | 136 | **Kind**: static method of [geojsonToWfst](#module_geojsonToWfst) 137 | **Returns**: string - a string wfs:Upate action. 138 | 139 | | Param | Type | Description | 140 | | --- | --- | --- | 141 | | features | [Array.<Feature>](#Feature) \| [FeatureCollection](#FeatureCollection) | features to update. These may pass in geometry_name, properties, and layer (overruled by params) and ns, layer, srsName (overruling params). | 142 | | params | [Params](#Params) | with optional properties, ns (namespace), layer, geometry_name, filter, typeName, whitelist. | 143 | 144 | 145 | 146 | ### geojsonToWfst.Delete(features, params) ⇒ string 147 | Creates a wfs:Delete action, creating a filter and typeName from feature ids 148 | if none are supplied. 149 | 150 | **Kind**: static method of [geojsonToWfst](#module_geojsonToWfst) 151 | **Returns**: string - a wfs:Delete string. 152 | 153 | | Param | Type | Description | 154 | | --- | --- | --- | 155 | | features | [Array.<Feature>](#Feature) \| [FeatureCollection](#FeatureCollection) \| [Feature](#Feature) | | 156 | | params | [Params](#Params) | optional parameter overrides. | 157 | | [params.ns] | string | @see Params.ns | 158 | | [params.layer] | string \| Object | @see Params.layer | 159 | | [params.typeName] | string | @see Params.typeName. This will be inferred from feature/params layer and ns if this is left undefined. | 160 | | [params.filter] | [filter](#filter) | @see Params.filter. This will be inferred from feature ids and layer(s) if left undefined (@see ensureFilter). | 161 | 162 | 163 | 164 | ### geojsonToWfst.Replace(features, params) ⇒ string 165 | Returns a string wfs:Replace action. 166 | 167 | **Kind**: static method of [geojsonToWfst](#module_geojsonToWfst) 168 | **Returns**: string - a string wfs:Replace action. 169 | 170 | | Param | Type | Description | 171 | | --- | --- | --- | 172 | | features | [Array.<Feature>](#Feature) \| [FeatureCollection](#FeatureCollection) \| [Feature](#Feature) | feature(s) to replace | 173 | | params | [Params](#Params) | with optional filter, inputFormat, srsName | 174 | 175 | 176 | 177 | ### geojsonToWfst.Transaction(actions, params) ⇒ string 178 | Wraps the input actions in a wfs:Transaction. 179 | 180 | **Kind**: static method of [geojsonToWfst](#module_geojsonToWfst) 181 | **Returns**: string - A wfs:transaction wrapping the input actions. 182 | **Throws**: 183 | 184 | - Error if `actions` is not an array of strings, a string, or 185 | {@see Insert, @see Update, @see Delete}, where each action are valid inputs 186 | to the eponymous function. 187 | 188 | 189 | | Param | Type | Description | 190 | | --- | --- | --- | 191 | | actions | Object \| Array.<string> \| string | an object mapping {Insert, Update, Delete} to feature(s) to pass to Insert, Update, Delete, or wfs:action string(s) to wrap in a transaction. | 192 | | params | [TransactionParams](#TransactionParams) | optional srsName, lockId, releaseAction, handle, inputFormat, version, and required nsAssignments, schemaLocations. | 193 | 194 | 195 | 196 | ## utils 197 | Common utilities for handling parameters for creation of WFS trasactions. 198 | 199 | 200 | * [utils](#module_utils) 201 | * [.useWhitelistIfAvailable(whitelist, properties, cb)](#module_utils.useWhitelistIfAvailable) 202 | * [.unpack(feature, params, args)](#module_utils.unpack) ⇒ Object 203 | * [.generateNsAssignments(nsAssignments, xml)](#module_utils.generateNsAssignments) ⇒ Object 204 | * [.generateSchemaLines(schemaLocations)](#module_utils.generateSchemaLines) ⇒ string 205 | * [.translateFeatures(features, params)](#module_utils.translateFeatures) ⇒ String 206 | 207 | 208 | 209 | ### utils.useWhitelistIfAvailable(whitelist, properties, cb) 210 | Iterates over the key-value pairs, filtering by a whitelist if available. 211 | 212 | **Kind**: static method of [utils](#module_utils) 213 | 214 | | Param | Type | Description | 215 | | --- | --- | --- | 216 | | whitelist | Array.<String> | a whitelist of property names | 217 | | properties | Object | an object mapping property names to values | 218 | | cb | function | a function to call on each (whitelisted key, value) pair | 219 | 220 | 221 | 222 | ### utils.unpack(feature, params, args) ⇒ Object 223 | Resolves attributes from feature, then params unless they are normally 224 | found in the feature 225 | 226 | **Kind**: static method of [utils](#module_utils) 227 | **Returns**: Object - an object mapping each named parameter to its resolved 228 | value 229 | 230 | | Param | Type | Description | 231 | | --- | --- | --- | 232 | | feature | Object | a geojson feature | 233 | | params | Object | an object of backup / override parameters | 234 | | args | Array.<String> | parameter names to resolve from feature or params | 235 | 236 | 237 | 238 | ### utils.generateNsAssignments(nsAssignments, xml) ⇒ Object 239 | Generates an object to be passed to @see xml.attrs xmlns:ns="uri" definitions 240 | for a wfs:Transaction 241 | 242 | **Kind**: static method of [utils](#module_utils) 243 | **Returns**: Object - an object mapping each ns to its URI as 'xmlns:ns' : 'URI'. 244 | **Throws**: 245 | 246 | - Error if any namespace used within `xml` is missing a URI 247 | definition 248 | 249 | 250 | | Param | Type | Description | 251 | | --- | --- | --- | 252 | | nsAssignments | Object | @see Params.nsAssignments | 253 | | xml | String | arbitrary xml. | 254 | 255 | 256 | 257 | ### utils.generateSchemaLines(schemaLocations) ⇒ string 258 | Returns a string alternating uri, whitespace, and the uri's schema's 259 | location. 260 | 261 | **Kind**: static method of [utils](#module_utils) 262 | **Returns**: string - a string that is a valid xsi:schemaLocation value. 263 | 264 | | Param | Type | Description | 265 | | --- | --- | --- | 266 | | schemaLocations | Object | an object mapping uri:schemalocation | 267 | 268 | 269 | 270 | ### utils.translateFeatures(features, params) ⇒ String 271 | Turns an array of geojson features into gml:_feature strings describing them. 272 | 273 | **Kind**: static method of [utils](#module_utils) 274 | **Returns**: String - a gml:_feature string. 275 | 276 | | Param | Type | Description | 277 | | --- | --- | --- | 278 | | features | [Array.<Feature>](#Feature) | an array of features to translate to gml:_features. | 279 | | params | [Params](#Params) | an object of backup / override parameters | 280 | 281 | 282 | 283 | ## xml 284 | xml utilities. 285 | 286 | 287 | * [xml](#module_xml) 288 | * [.wfs](#module_xml.wfs) ⇒ String 289 | * [.attrs(attrs)](#module_xml.attrs) ⇒ String 290 | * [.tag(ns, tagName, attrsObj, inner)](#module_xml.tag) ⇒ String 291 | * [.idFilter(lyr, id)](#module_xml.idFilter) ⇒ String 292 | * [.escape(input)](#module_xml.escape) ⇒ String 293 | 294 | 295 | 296 | ### xml.wfs ⇒ String 297 | Shorthand for creating a wfs xml tag. 298 | 299 | **Kind**: static constant of [xml](#module_xml) 300 | **Returns**: String - a wfs element. 301 | 302 | | Param | Type | Description | 303 | | --- | --- | --- | 304 | | tagName | String | a valid wfs tag name. | 305 | | attrsObj | Object | @see xml.attrs. | 306 | | inner | String | @see xml.tag. | 307 | 308 | 309 | 310 | ### xml.attrs(attrs) ⇒ String 311 | Turns an object into a string of xml attribute key-value pairs. 312 | 313 | **Kind**: static method of [xml](#module_xml) 314 | **Returns**: String - a string of xml attribute key-value pairs 315 | 316 | | Param | Type | Description | 317 | | --- | --- | --- | 318 | | attrs | Object | an object mapping attribute names to attribute values | 319 | 320 | 321 | 322 | ### xml.tag(ns, tagName, attrsObj, inner) ⇒ String 323 | Creates a string xml tag. 324 | 325 | **Kind**: static method of [xml](#module_xml) 326 | **Returns**: String - an xml string. 327 | 328 | | Param | Type | Description | 329 | | --- | --- | --- | 330 | | ns | String | the tag's xml namespace abbreviation. | 331 | | tagName | String | the tag name. | 332 | | attrsObj | Object | @see xml.attrs. | 333 | | inner | String | inner xml. | 334 | 335 | 336 | 337 | ### xml.idFilter(lyr, id) ⇒ String 338 | Creates a fes:ResourceId filter from a layername and id 339 | 340 | **Kind**: static method of [xml](#module_xml) 341 | **Returns**: String - a filter-ecoding of the filter. 342 | 343 | | Param | Type | Description | 344 | | --- | --- | --- | 345 | | lyr | String | layer name of the filtered feature | 346 | | id | String | feature id | 347 | 348 | 349 | 350 | ### xml.escape(input) ⇒ String 351 | Creates an xml-safe string from a given input string 352 | 353 | **Kind**: static method of [xml](#module_xml) 354 | **Returns**: String - XML-safe string 355 | 356 | | Param | Type | Description | 357 | | --- | --- | --- | 358 | | input | String | String to escape | 359 | 360 | 361 | 362 | ## filter(filter, features, params) ⇒ String 363 | Builds a filter from feature ids if one is not already input. 364 | 365 | **Kind**: global function 366 | **Returns**: String - A filter, or the input filter if it was a string. 367 | 368 | | Param | Type | Description | 369 | | --- | --- | --- | 370 | | filter | String | a possible string filter | 371 | | features | Array.<Object> | an array of geojson feature objects | 372 | | params | Object | an object of backup / override parameters | 373 | 374 | 375 | 376 | ## Params : Object 377 | An object containing optional named parameters. 378 | 379 | **Kind**: global typedef 380 | **Properties** 381 | 382 | | Name | Type | Description | 383 | | --- | --- | --- | 384 | | ns | string | an xml namespace alias. | 385 | | layer | string \| Object | a string layer name or {id}, where id is the layer name | 386 | | geometry_name | string | the name of the feature geometry field. | 387 | | properties | Object | an object mapping feature field names to feature properties | 388 | | id | string | a string feature id. | 389 | | whitelist | Array.<string> | an array of string field names to use from @see Params.properties | 390 | | inputFormat | string | inputFormat, as specified at [OGC 09-025r2 § 7.6.5.4](http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#65). | 391 | | srsName | string | srsName, as specified at [OGC 09-025r2 § 7.6.5.5](http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#66). if undefined, the gml3 module will default to 'EPSG:4326'. | 392 | | handle | string | handle parameter, as specified at [OGC 09-025r2 § 7.6.2.6 ](http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#44) | 393 | | filter | string | a string fes:Filter. | 394 | | typeName | string | a string specifying the feature type within its namespace. See [09-025r2 § 7.9.2.4.1](http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#90). | 395 | | schemaLocations | Object | an object mapping uri to schema locations | 396 | | nsAssignments | Object | an object mapping ns to uri | 397 | 398 | 399 | 400 | ## TransactionParams : Object 401 | An object containing optional named parameters for a transaction in addition 402 | to parameters used elsewhere. 403 | 404 | **Kind**: global typedef 405 | **Extends**: [Params](#Params) 406 | **Properties** 407 | 408 | | Name | Type | Description | 409 | | --- | --- | --- | 410 | | lockId | string | lockId parameter, as specified at [OGC 09-025r2 § 15.2.3.1.2](http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#277). | 411 | | releaseAction | string | releaseAction parameter, as specified at [OGC 09-025r2 § 15.2.3.2](http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#278). | 412 | 413 | 414 | 415 | ## Feature : Object 416 | A GeoJSON feature with the following optional foreign members (see 417 | [rfc7965 § 6](https://tools.ietf.org/html/rfc7946#section-6)). 418 | or an object with some of the following members. 419 | Members of Feature will be used over those in Params except for layer, id, 420 | and properties. 421 | 422 | **Kind**: global typedef 423 | **Extends**: [Params](#Params) 424 | **Properties** 425 | 426 | | Name | Type | Description | 427 | | --- | --- | --- | 428 | | geometry | Object | a GeoJSON geometry. | 429 | | type | string | 'Feature'. | 430 | 431 | **Example** 432 | ```js 433 | {'id':'tasmania_roads.1', 'typeName':'topp:tasmania_roadsType'} 434 | // can be passed to Delete 435 | ``` 436 | 437 | 438 | ## FeatureCollection : Object 439 | a GeoJSON FeatureCollection with optional foreign members as in Feature. 440 | 441 | **Kind**: global typedef 442 | **Extends**: [Feature](#Feature) 443 | **Properties** 444 | 445 | | Name | Type | Description | 446 | | --- | --- | --- | 447 | | type | string | 'FeatureCollection'. | 448 | | features | [Array.<Feature>](#Feature) | an array of GeoJSON Features. | 449 | 450 | -------------------------------------------------------------------------------- /dist/es6.es.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /** 3 | * reorder coordinates to lat, lng iff config.coordinateOrder is false. 4 | * @param {Number[]} coords An array of coordinates, [lng, lat, ...etc] 5 | * @return {Number[]} An array of coordinates in the correct order. 6 | */ 7 | 8 | function orderCoords(coords) { 9 | { 10 | return coords; 11 | } 12 | 13 | if (coords[2]) { 14 | return [coords[1], coords[0], coords[2]]; 15 | } 16 | 17 | return coords.reverse(); 18 | } 19 | /** @private*/ 20 | 21 | 22 | function attrs(attrMappings) { 23 | // return Object.entries() 24 | let results = ''; 25 | 26 | for (let attrName in attrMappings) { 27 | let value = attrMappings[attrName]; 28 | results += value ? ` ${attrName}="${value}"` : ''; 29 | } 30 | 31 | return results; 32 | } 33 | /** 34 | * Optional parameters for conversion of geojson to gml geometries 35 | * @typedef {Object} Params 36 | * @property {?String} params.srsName as string specifying SRS, e.g. 'EPSG:4326'. Only applies to multigeometries. 37 | * @property {?Number[]|?String[]} params.gmlIds an array of number/string gml:ids of the member geometries. 38 | * @property {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 39 | */ 40 | 41 | /** 42 | * A handler to compile geometries to multigeometries 43 | * @function 44 | * @param {String} name the name of the target multigeometry 45 | * @param {String} memberName the gml:tag of each multigeometry member. 46 | * @param {Object[]|Array} geom an array of geojson geometries 47 | * @param {String|Number} gmlId the gml:id of the multigeometry 48 | * @param {Params} params optional parameters. Omit gmlIds at your own risk, however. 49 | * @returns {String} a string containing gml describing the input multigeometry 50 | * @throws {Error} if a member geometry cannot be converted to gml 51 | */ 52 | 53 | 54 | function multi(name, memberName, membercb, geom, gmlId, params = {}) { 55 | var { 56 | srsName, 57 | gmlIds 58 | } = params; 59 | let multi = ``; 63 | multi += ``; 64 | geom.forEach(function (member, i) { 65 | let _gmlId = member.id || (gmlIds || [])[i] || ''; 66 | 67 | if (name == 'MultiGeometry') { 68 | let memberType = member.type; 69 | member = member.coordinates; 70 | multi += membercb[memberType](member, _gmlId, params); 71 | } else { 72 | multi += membercb(member, _gmlId, params); 73 | } 74 | }); 75 | multi += ``; 76 | return multi + ``; 77 | } 78 | /** 79 | * Converts an input geojson Point geometry to gml 80 | * @function 81 | * @param {Number[]} coords the coordinates member of the geojson geometry 82 | * @param {String|Number} gmlId the gml:id 83 | * @param {Params} params optional parameters 84 | * @returns {String} a string containing gml representing the input geometry 85 | */ 86 | 87 | 88 | function point(coords, gmlId, params = {}) { 89 | var { 90 | srsName: srsName, 91 | srsDimension: srsDimension 92 | } = params; 93 | return `` + `` + orderCoords(coords).join(' ') + '' + ''; 99 | } 100 | /** 101 | * Converts an input geojson LineString geometry to gml 102 | * @function 103 | * @param {Number[][]} coords the coordinates member of the geojson geometry 104 | * @param {String|Number} gmlId the gml:id 105 | * @param {Params} params optional parameters 106 | * @returns {String} a string containing gml representing the input geometry 107 | */ 108 | 109 | function lineString(coords, gmlId, params = {}) { 110 | var { 111 | srsName: srsName, 112 | srsDimension: srsDimension 113 | } = params; 114 | return `` + `` + coords.map(e => orderCoords(e).join(' ')).join(' ') + '' + ''; 120 | } 121 | /** 122 | * Converts an input geojson LinearRing member of a polygon geometry to gml 123 | * @function 124 | * @param {Number[][]} coords the coordinates member of the geojson geometry 125 | * @param {String|Number} gmlId the gml:id 126 | * @param {Params} params optional parameters 127 | * @returns {String} a string containing gml representing the input geometry 128 | */ 129 | 130 | function linearRing(coords, gmlId, params = {}) { 131 | var { 132 | srsName: srsName, 133 | srsDimension: srsDimension 134 | } = params; 135 | return `` + `` + coords.map(e => orderCoords(e).join(' ')).join(' ') + '' + ''; 141 | } 142 | /** 143 | * Converts an input geojson Polygon geometry to gml 144 | * @function 145 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 146 | * @param {String|Number} gmlId the gml:id 147 | * @param {Params} params optional parameters 148 | * @returns {String} a string containing gml representing the input geometry 149 | */ 150 | 151 | function polygon(coords, gmlId, params = {}) { 152 | // geom.coordinates are arrays of LinearRings 153 | var { 154 | srsName 155 | } = params; 156 | let polygon = `` + '' + linearRing(coords[0]) + ''; 160 | 161 | if (coords.length >= 2) { 162 | for (let ring of coords.slice(1)) { 163 | polygon += '' + linearRing(ring) + ''; 164 | } 165 | } 166 | 167 | polygon += ''; 168 | return polygon; 169 | } 170 | /** 171 | * Converts an input geojson MultiPoint geometry to gml 172 | * @function 173 | * @param {Number[][]} coords the coordinates member of the geojson geometry 174 | * @param {String|Number} gmlId the gml:id 175 | * @param {Params} params optional parameters 176 | * @returns {String} a string containing gml representing the input geometry 177 | */ 178 | 179 | function multiPoint(coords, gmlId, params = {}) { 180 | return multi('MultiPoint', 'pointMembers', point, coords, gmlId, params); 181 | } 182 | /** 183 | * Converts an input geojson MultiLineString geometry to gml 184 | * @function 185 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 186 | * @param {String|Number} gmlId the gml:id 187 | * @param {Params} params optional parameters 188 | * @returns {String} a string containing gml representing the input geometry 189 | */ 190 | 191 | function multiLineString(coords, gmlId, params = {}) { 192 | return multi('MultiCurve', 'curveMembers', lineString, coords, gmlId, params); 193 | } 194 | /** 195 | * Converts an input geojson MultiPolygon geometry to gml 196 | * @function 197 | * @param {Number[][][][]} coords the coordinates member of the geojson geometry 198 | * @param {String|Number} gmlId the gml:id 199 | * @param {Params} params optional parameters 200 | * @returns {String} a string containing gml representing the input geometry 201 | */ 202 | 203 | function multiPolygon(coords, gmlId, params = {}) { 204 | return multi('MultiSurface', 'surfaceMembers', polygon, coords, gmlId, params); 205 | } 206 | /** 207 | * A helper to de-camelcase this module's geometry conversion methods 208 | * @param {Object} obj a mapping of camelcase geometry types to converter functions 209 | * @return {Object} a mapping of capitalized geometry types to converter functions 210 | * @example 211 | * makeConverter({lineString}) 212 | * // returns {LineString: lineString} 213 | */ 214 | 215 | function makeConverter(obj) { 216 | return Object.entries(obj).map(([type, converter]) => { 217 | return { 218 | [type[0].toUpperCase() + type.slice(1)]: converter 219 | }; 220 | }).reduce((a, b) => Object.assign(a, b), {}); 221 | } 222 | /** 223 | * A helper to map geometry types to converter functions. 224 | * @function 225 | * @param {Object} obj an object mapping camelcase-d geometry type names to 226 | * converter functions for that type. 227 | * @example 228 | * import {point, lineString} from 'geojson-to-gml-3'; 229 | * const geomToGml = makeTranslator({point, lineString}); 230 | * geomToGml({type: 'Point', coordinates: [0, 0]}); 231 | */ 232 | 233 | 234 | function makeTranslator(obj) { 235 | const converter = makeConverter(obj); 236 | return function (geom, gmlId, params) { 237 | const warn = () => new Error(`unkown: ${geom.type} ` + [...arguments].join()); 238 | 239 | const convert = converter[geom.type] || warn; 240 | return convert(geom.coordinates || geom.geometries, gmlId, params); 241 | }; 242 | } 243 | /** 244 | * a namespace to switch between geojson-handling functions by geojson.type 245 | * @const 246 | * @type {Object} 247 | */ 248 | 249 | const allTypes = makeConverter({ 250 | point, 251 | lineString, 252 | linearRing, 253 | polygon, 254 | multiPoint, 255 | multiLineString, 256 | multiPolygon 257 | }); 258 | /** 259 | * Converts an input geojson GeometryCollection geometry to gml 260 | * @function 261 | * @param {Object[]} coords the coordinates member of the geojson geometry 262 | * @param {String|Number} gmlId the gml:id 263 | * @param {Object} params optional parameters 264 | * @param {?String} params.srsName as string specifying SRS 265 | * @param {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 266 | * @returns {String} a string containing gml representing the input geometry 267 | */ 268 | 269 | function geometryCollection(geoms, gmlId, params = {}) { 270 | return multi('MultiGeometry', 'geometryMembers', allTypes, geoms, gmlId, params); 271 | } 272 | /** 273 | * Translates any geojson geometry into GML 3.2.1 274 | * @public 275 | * @function 276 | * @param {Object} geom a geojson geometry object 277 | * @param {?Array} geom.coordinates the nested array of coordinates forming the geometry 278 | * @param {?Object[]} geom.geometries for a GeometryCollection only, the array of member geometry objects 279 | * @param {String|Number} gmlId the gml:id of the geometry 280 | * @param {object} params optional parameters 281 | * @param {?String} params.srsName a string specifying the SRS 282 | * @param {?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 283 | * @param {?Number[]|?String[]} gmlIds an array of number/string gml:ids of the member geometries of a multigeometry. 284 | * @returns {String} a valid gml string describing the input geojson geometry 285 | */ 286 | 287 | const geomToGml = makeTranslator(Object.assign({ 288 | geometryCollection 289 | }, allTypes)); 290 | 291 | /** 292 | * common checks, coersions, and informative errors/ warnings 293 | * @module ensure 294 | */ 295 | 296 | /** 297 | * Ensures the result is an array. 298 | * @private 299 | * @function 300 | * @param {Feature|Feature[]|FeatureCollection} maybe a GeoJSON 301 | * FeatureCollection, Feature, or an array of Features. 302 | * @return {Feature[]} 303 | */ 304 | const array = (...maybe) => (maybe[0].features || [].concat(...maybe)).filter(Boolean); 305 | /** 306 | * Ensures a layer.id format of an input id. 307 | * @function 308 | * @param {String} lyr layer name 309 | * @param {String} id id, possibly already in correct layer.id format. 310 | * @return {String} a correctly-formatted gml:id 311 | */ 312 | 313 | const id = (lyr, id) => /\./.exec(id || '') ? id : `${lyr}.${id}`; 314 | /** 315 | * return a correctly-formatted typeName 316 | * @function 317 | * @param {String} ns namespace 318 | * @param {String} layer layer name 319 | * @param {String} typeName typeName to check 320 | * @return {String} a correctly-formatted typeName 321 | * @throws {Error} if typeName it cannot form a typeName from ns and layer 322 | */ 323 | 324 | const typeName = (ns, layer, typeName) => { 325 | if (!typeName && !(ns && layer)) { 326 | throw new Error(`no typename possible: ${JSON.stringify({ 327 | typeName, 328 | ns, 329 | layer 330 | }, null, 2)}`); 331 | } 332 | 333 | return typeName || `${layer}`; 334 | }; // http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286 335 | 336 | /** 337 | * xml utilities. 338 | * @module xml 339 | */ 340 | 341 | /** 342 | * Turns an object into a string of xml attribute key-value pairs. 343 | * @function 344 | * @param {Object} attrs an object mapping attribute names to attribute values 345 | * @return {String} a string of xml attribute key-value pairs 346 | */ 347 | 348 | function attrs$1(attrs) { 349 | return Object.keys(attrs).map(a => attrs[a] ? ` ${a}="${escape(attrs[a])}"` : '').join(''); 350 | } 351 | /** 352 | * Creates a string xml tag. 353 | * @function 354 | * @param {String} ns the tag's xml namespace abbreviation. 355 | * @param {String} tagName the tag name. 356 | * @param {Object} attrsObj @see xml.attrs. 357 | * @param {String} inner inner xml. 358 | * @return {String} an xml string. 359 | */ 360 | 361 | function tag(ns, tagName, attrsObj, inner) { 362 | let tag = (ns ? `${ns}:` : '') + tagName; 363 | 364 | if (tagName) { 365 | return `<${tag}${attrs$1(attrsObj)}${inner !== null ? `>${inner}`; 366 | } else { 367 | throw new Error('no tag supplied ' + JSON.stringify({ 368 | ns, 369 | tagName, 370 | attrsObj, 371 | inner 372 | }, null, 2)); 373 | } 374 | } 375 | /** 376 | * Shorthand for creating a wfs xml tag. 377 | * @param {String} tagName a valid wfs tag name. 378 | * @param {Object} attrsObj @see xml.attrs. 379 | * @param {String} inner @see xml.tag. 380 | * @return {String} a wfs element. 381 | */ 382 | 383 | const wfs = (tagName, attrsObj, inner) => tag('wfs', tagName, attrsObj, inner); 384 | /** 385 | * Creates a fes:ResourceId filter from a layername and id 386 | * @function 387 | * @param {String} lyr layer name of the filtered feature 388 | * @param {String} id feature id 389 | * @return {String} a filter-ecoding of the filter. 390 | */ 391 | 392 | const idFilter = (lyr, id$$1) => { 393 | return ``; 394 | }; 395 | /** 396 | * Creates an xml-safe string from a given input string 397 | * @function 398 | * @param {String} input String to escape 399 | * @return {String} XML-safe string 400 | */ 401 | 402 | function escape(input) { 403 | if (typeof input !== 'string') { 404 | // Backup check for non-strings 405 | return input; 406 | } 407 | 408 | const output = input.replace(/[<>&'"]/g, char => { 409 | switch (char) { 410 | case '<': 411 | return '<'; 412 | 413 | case '>': 414 | return '>'; 415 | 416 | case '&': 417 | return '&'; 418 | 419 | case `'`: 420 | return '''; 421 | 422 | case '"': 423 | return '"'; 424 | } 425 | }); 426 | return output; 427 | } 428 | 429 | /* eslint-disable camelcase */ 430 | 431 | /** 432 | * Common utilities for handling parameters for creation of WFS trasactions. 433 | * @module utils 434 | */ 435 | 436 | /** 437 | * Iterates over the key-value pairs, filtering by a whitelist if available. 438 | * @function 439 | * @param {Array} whitelist a whitelist of property names 440 | * @param {Object} properties an object mapping property names to values 441 | * @param {Function} cb a function to call on each (whitelisted key, value) pair 442 | */ 443 | 444 | const useWhitelistIfAvailable = (whitelist, properties, cb) => { 445 | for (let prop of whitelist || Object.keys(properties)) { 446 | const val = properties[prop]; 447 | 448 | if (Number.isNaN(val)) { 449 | throw new Error('NaN is not allowed.'); 450 | } 451 | 452 | if (val !== undefined) { 453 | cb(prop, val); 454 | } 455 | } 456 | }; 457 | const featureMembers = new Set(['properties', 'geometry', 'id', 'layer']); 458 | /** 459 | * Resolves attributes from feature, then params unless they are normally 460 | * found in the feature 461 | * @param {Object} feature a geojson feature 462 | * @param {Object} params an object of backup / override parameters 463 | * @param {Array} args parameter names to resolve from feature or 464 | * params 465 | * @return {Object} an object mapping each named parameter to its resolved 466 | * value 467 | */ 468 | 469 | function unpack(feature, params, ...args) { 470 | let results = {}; 471 | 472 | for (let arg of args) { 473 | if (arg === 'layer') { 474 | results[arg] = (params.layer || {}).id || params.layer || (feature.layer || {}).id || feature.layer || ''; 475 | } else if (!featureMembers.has(arg)) { 476 | results[arg] = feature[arg] || params[arg] || ''; 477 | } else { 478 | results[arg] = params[arg] || feature[arg] || ''; 479 | } 480 | } 481 | 482 | return results; 483 | } 484 | /** 485 | * Generates an object to be passed to @see xml.attrs xmlns:ns="uri" definitions 486 | * for a wfs:Transaction 487 | * @param {Object} nsAssignments @see Params.nsAssignments 488 | * @param {String} xml arbitrary xml. 489 | * @return {Object} an object mapping each ns to its URI as 'xmlns:ns' : 'URI'. 490 | * @throws {Error} if any namespace used within `xml` is missing a URI 491 | * definition 492 | */ 493 | 494 | function generateNsAssignments(nsAssignments, xml) { 495 | let attrs = {}; 496 | 497 | const makeNsAssignment = (ns, uri) => attrs[`xmlns:${ns}`] = uri; 498 | 499 | Object.keys(nsAssignments).forEach(ns => { 500 | makeNsAssignment(ns, nsAssignments[ns]); 501 | }); // check all ns's assigned 502 | 503 | let re = /(<|typeName=")(\w+):/g; 504 | let arr; 505 | let allNamespaces = new Set(); 506 | 507 | while ((arr = re.exec(xml)) !== null) { 508 | allNamespaces.add(arr[2]); 509 | } 510 | 511 | if (allNamespaces.has('fes')) { 512 | makeNsAssignment('fes', 'http://www.opengis.net/fes/2.0'); 513 | } 514 | makeNsAssignment('xsi', 'http://www.w3.org/2001/XMLSchema-instance'); 515 | makeNsAssignment('gml', 'http://www.opengis.net/gml/3.2'); 516 | makeNsAssignment('wfs', 'http://www.opengis.net/wfs/2.0'); 517 | 518 | for (let ns of allNamespaces) { 519 | if (!attrs['xmlns:' + ns]) { 520 | throw new Error(`unassigned namespace ${ns}`); 521 | } 522 | } 523 | /* , schemaLocations*/ 524 | 525 | 526 | return attrs; 527 | } 528 | /** 529 | * Returns a string alternating uri, whitespace, and the uri's schema's 530 | * location. 531 | * @param {Object} schemaLocations an object mapping uri:schemalocation 532 | * @return {string} a string that is a valid xsi:schemaLocation value. 533 | */ 534 | 535 | function generateSchemaLines(schemaLocations = {}) { 536 | // TODO: add ns assignment check 537 | schemaLocations['http://www.opengis.net/wfs/2.0'] = 'http://schemas.opengis.net/wfs/2.0/wfs.xsd'; 538 | let schemaLines = []; 539 | Object.entries(schemaLocations).forEach(entry => schemaLines.push(entry.join('\n'))); 540 | return schemaLines.join('\n'); 541 | } 542 | /** 543 | * Turns an array of geojson features into gml:_feature strings describing them. 544 | * @function 545 | * @param {Feature[]} features an array of features to translate to 546 | * gml:_features. 547 | * @param {Params} params an object of backup / override parameters 548 | * @return {String} a gml:_feature string. 549 | */ 550 | 551 | function translateFeatures(features, params = {}) { 552 | let inner = ''; 553 | let { 554 | srsName, 555 | srsDimension 556 | } = params; 557 | 558 | for (let feature of features) { 559 | // TODO: add whitelist support 560 | let { 561 | ns, 562 | layer, 563 | geometry_name, 564 | properties, 565 | id: id$$1, 566 | whitelist 567 | } = unpack(feature, params, 'ns', 'layer', 'geometry_name', 'properties', 'id', 'whitelist'); 568 | let fields = ''; 569 | 570 | if (geometry_name) { 571 | fields += tag(ns, geometry_name, {}, geomToGml(feature.geometry, '', { 572 | srsName, 573 | srsDimension 574 | })); 575 | } 576 | 577 | useWhitelistIfAvailable(whitelist, properties, (prop, val) => { 578 | if (val === null) { 579 | return fields; 580 | } 581 | 582 | return fields += tag(ns, prop, {}, escape(properties[prop])); 583 | }); 584 | inner += tag(ns, layer, { 585 | 'gml:id': id(layer, id$$1) 586 | }, fields); 587 | } 588 | 589 | return inner; 590 | } 591 | 592 | /** 593 | * Builds a filter from feature ids if one is not already input. 594 | * @function 595 | * @param {?String} filter a possible string filter 596 | * @param {Array} features an array of geojson feature objects 597 | * @param {Object} params an object of backup / override parameters 598 | * @return {String} A filter, or the input filter if it was a string. 599 | */ 600 | 601 | function filter(filter, features, params) { 602 | if (!filter) { 603 | filter = ''; 604 | 605 | for (let feature of features) { 606 | let layer = unpack(feature, params); 607 | filter += idFilter(layer, feature.id); 608 | } 609 | 610 | return `${filter}`; 611 | } else { 612 | return filter; 613 | } 614 | } 615 | 616 | /* eslint-disable camelcase, new-cap */ 617 | /** 618 | * Returns a wfs:Insert tag wrapping a translated feature 619 | * @function 620 | * @param {Feature[]|FeatureCollection|Feature} features Feature(s) to pass to 621 | * @see translateFeatures 622 | * @param {Params} params to be passed to @see translateFeatures, with optional 623 | * inputFormat, srsName, handle for the wfs:Insert tag. 624 | * @return {string} a wfs:Insert string. 625 | */ 626 | 627 | function Insert(features, params = {}) { 628 | features = array(features); 629 | let { 630 | inputFormat, 631 | srsName, 632 | handle 633 | } = params; 634 | 635 | if (!features.length) { 636 | console.warn('no features supplied'); 637 | return ''; 638 | } 639 | 640 | let toInsert = translateFeatures(features, params); 641 | return tag('wfs', 'Insert', { 642 | inputFormat, 643 | srsName, 644 | handle 645 | }, toInsert); 646 | } 647 | /** 648 | * Updates the input features in bulk with params.properties or by id. 649 | * @param {Feature[]|FeatureCollection} features features to update. These may 650 | * pass in geometry_name, properties, and layer (overruled by params) and 651 | * ns, layer, srsName (overruling params). 652 | * @param {Params} params with optional properties, ns (namespace), layer, 653 | * geometry_name, filter, typeName, whitelist. 654 | * @return {string} a string wfs:Upate action. 655 | */ 656 | 657 | function Update(features, params = {}) { 658 | features = array(features); 659 | /** 660 | * makes a wfs:Property string containg a wfs:ValueReference, wfs:Value pair. 661 | * @private 662 | * @function 663 | * @memberof Update~ 664 | * @param {string} prop the field/property name 665 | * @param {string} val the field/property value 666 | * @param {string} action one of 'insertBefore', 'insertAfter', 'remove', 667 | * 'replace'. See [OGC 09-025r2 § 15.2.5.2.1]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286}. 668 | * `action` would delete or modify the order of fields within the remote 669 | * feature. There is currently no way to input `action,` since wfs:Update's 670 | * default action, 'replace', is sufficient. 671 | * @return {string} a wfs:Property(wfs:ValueReference) pair. 672 | */ 673 | 674 | const makeKvp = (prop, val, action$$1) => { 675 | let value = ''; 676 | 677 | if (val === null) { 678 | value = wfs('Value', { 679 | 'xsi:nil': true 680 | }, ''); 681 | } else if (val !== undefined) { 682 | value = wfs('Value', {}, val); 683 | } 684 | 685 | return wfs('Property', {}, wfs('ValueReference', { 686 | action: action$$1 687 | }, prop) + value); 688 | }; 689 | 690 | if (params.properties) { 691 | let { 692 | /* handle, */ 693 | inputFormat, 694 | filter: filter$$1, 695 | typeName: typeName$$1, 696 | whitelist 697 | } = params; 698 | let { 699 | srsName, 700 | ns, 701 | layer, 702 | geometry_name 703 | } = unpack(features[0] || {}, params, 'srsName', 'ns', 'layer', 'geometry_name'); 704 | typeName$$1 = typeName(ns, layer, typeName$$1); 705 | filter$$1 = filter(filter$$1, features, params); 706 | 707 | if (!filter$$1 && !features.length) { 708 | console.warn('neither features nor filter supplied'); 709 | return ''; 710 | } 711 | 712 | let fields = ''; 713 | useWhitelistIfAvailable( // TODO: action attr 714 | whitelist, params.properties, (k, v) => fields += makeKvp(k, escape(v))); 715 | 716 | if (geometry_name) { 717 | fields += makeKvp(geometry_name, tag(ns, geometry_name, {}, geomToGml(params.geometry, '', { 718 | srsName 719 | }))); 720 | } 721 | 722 | return wfs('Update', { 723 | inputFormat, 724 | srsName, 725 | typeName: typeName$$1 726 | }, fields + filter$$1); 727 | } else { 728 | // encapsulate each update in its own Update tag 729 | return features.map(f => Update(f, Object.assign({}, params, { 730 | properties: f.properties 731 | }))).join(''); 732 | } 733 | } 734 | /** 735 | * Creates a wfs:Delete action, creating a filter and typeName from feature ids 736 | * if none are supplied. 737 | * @param {Feature[]|FeatureCollection|Feature} features 738 | * @param {Params} params optional parameter overrides. 739 | * @param {string} [params.ns] @see Params.ns 740 | * @param {string|Object} [params.layer] @see Params.layer 741 | * @param {string} [params.typeName] @see Params.typeName. This will be inferred 742 | * from feature/params layer and ns if this is left undefined. 743 | * @param {filter} [params.filter] @see Params.filter. This will be inferred 744 | * from feature ids and layer(s) if left undefined (@see ensureFilter). 745 | * @return {string} a wfs:Delete string. 746 | */ 747 | 748 | function Delete(features, params = {}) { 749 | features = array(features); 750 | let { 751 | filter: filter$$1, 752 | typeName: typeName$$1 753 | } = params; // TODO: recur & encapsulate by typeName 754 | 755 | let { 756 | ns, 757 | layer 758 | } = unpack(features[0] || {}, params, 'layer', 'ns'); 759 | typeName$$1 = typeName(ns, layer, typeName$$1); 760 | filter$$1 = filter(filter$$1, features, params); 761 | return wfs('Delete', { 762 | typeName: typeName$$1 763 | }, filter$$1); 764 | } 765 | /** 766 | * Returns a string wfs:Replace action. 767 | * @param {Feature[]|FeatureCollection|Feature} features feature(s) to replace 768 | * @param {Params} params with optional filter, inputFormat, srsName 769 | * @return {string} a string wfs:Replace action. 770 | */ 771 | 772 | function Replace(features, params = {}) { 773 | features = array(features); 774 | let { 775 | filter: filter$$1, 776 | inputFormat, 777 | srsName 778 | } = unpack(features[0] || {}, params || {}, 'filter', 'inputFormat', 'srsName'); 779 | let replacements = translateFeatures([features[0]].filter(f => f), params || { 780 | srsName 781 | }); 782 | filter$$1 = filter(filter$$1, features, params); 783 | return wfs('Replace', { 784 | inputFormat, 785 | srsName 786 | }, replacements + filter$$1); 787 | } 788 | /** 789 | * Wraps the input actions in a wfs:Transaction. 790 | * @param {Object|string[]|string} actions an object mapping {Insert, Update, 791 | * Delete} to feature(s) to pass to Insert, Update, Delete, or wfs:action 792 | * string(s) to wrap in a transaction. 793 | * @param {TransactionParams} params optional srsName, lockId, releaseAction, 794 | * handle, inputFormat, version, and required nsAssignments, schemaLocations. 795 | * @return {string} A wfs:transaction wrapping the input actions. 796 | * @throws {Error} if `actions` is not an array of strings, a string, or 797 | * {@see Insert, @see Update, @see Delete}, where each action are valid inputs 798 | * to the eponymous function. 799 | */ 800 | 801 | function Transaction(actions, params = {}) { 802 | const transactionParams = ['srsName', 'lockId', 'releaseAction', 'handle']; 803 | let { 804 | version, 805 | // optional 806 | nsAssignments = {} // required 807 | 808 | } = params; // let converter = {Insert, Update, Delete}; 809 | 810 | let { 811 | insert: toInsert, 812 | update: toUpdate, 813 | delete: toDelete 814 | } = actions || {}; 815 | let finalActions = ''; // processedActions would be more accurate 816 | 817 | if (Array.isArray(actions) && actions.every(v => typeof v == 'string')) { 818 | finalActions += actions.join(''); 819 | } else if (typeof actions == 'string') { 820 | finalActions = actions; 821 | } else if ([toInsert, toUpdate, toDelete].some(e => e)) { 822 | finalActions += Insert(toInsert, params) + Update(toUpdate, params) + Delete(toDelete, params); 823 | } else { 824 | throw new Error(`unexpected input: ${JSON.stringify(actions)}`); 825 | } // generate schemaLocation, xmlns's 826 | 827 | 828 | let attrs = generateNsAssignments(nsAssignments, actions); 829 | attrs['xsi:schemaLocation'] = generateSchemaLines(params.schemaLocations); 830 | attrs['service'] = 'WFS'; 831 | attrs['version'] = /2\.0\.\d+/.exec(version || '') ? version : '2.0.0'; 832 | transactionParams.forEach(param => { 833 | if (params[param]) { 834 | attrs[param] = params[param]; 835 | } 836 | }); 837 | return wfs('Transaction', attrs, finalActions); 838 | } 839 | 840 | export { Insert, Update, Delete, Replace, Transaction }; 841 | -------------------------------------------------------------------------------- /dist/es6.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | /* eslint-disable no-console */ 6 | /** 7 | * reorder coordinates to lat, lng iff config.coordinateOrder is false. 8 | * @param {Number[]} coords An array of coordinates, [lng, lat, ...etc] 9 | * @return {Number[]} An array of coordinates in the correct order. 10 | */ 11 | 12 | function orderCoords(coords) { 13 | { 14 | return coords; 15 | } 16 | 17 | if (coords[2]) { 18 | return [coords[1], coords[0], coords[2]]; 19 | } 20 | 21 | return coords.reverse(); 22 | } 23 | /** @private*/ 24 | 25 | 26 | function attrs(attrMappings) { 27 | // return Object.entries() 28 | let results = ''; 29 | 30 | for (let attrName in attrMappings) { 31 | let value = attrMappings[attrName]; 32 | results += value ? ` ${attrName}="${value}"` : ''; 33 | } 34 | 35 | return results; 36 | } 37 | /** 38 | * Optional parameters for conversion of geojson to gml geometries 39 | * @typedef {Object} Params 40 | * @property {?String} params.srsName as string specifying SRS, e.g. 'EPSG:4326'. Only applies to multigeometries. 41 | * @property {?Number[]|?String[]} params.gmlIds an array of number/string gml:ids of the member geometries. 42 | * @property {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 43 | */ 44 | 45 | /** 46 | * A handler to compile geometries to multigeometries 47 | * @function 48 | * @param {String} name the name of the target multigeometry 49 | * @param {String} memberName the gml:tag of each multigeometry member. 50 | * @param {Object[]|Array} geom an array of geojson geometries 51 | * @param {String|Number} gmlId the gml:id of the multigeometry 52 | * @param {Params} params optional parameters. Omit gmlIds at your own risk, however. 53 | * @returns {String} a string containing gml describing the input multigeometry 54 | * @throws {Error} if a member geometry cannot be converted to gml 55 | */ 56 | 57 | 58 | function multi(name, memberName, membercb, geom, gmlId, params = {}) { 59 | var { 60 | srsName, 61 | gmlIds 62 | } = params; 63 | let multi = ``; 67 | multi += ``; 68 | geom.forEach(function (member, i) { 69 | let _gmlId = member.id || (gmlIds || [])[i] || ''; 70 | 71 | if (name == 'MultiGeometry') { 72 | let memberType = member.type; 73 | member = member.coordinates; 74 | multi += membercb[memberType](member, _gmlId, params); 75 | } else { 76 | multi += membercb(member, _gmlId, params); 77 | } 78 | }); 79 | multi += ``; 80 | return multi + ``; 81 | } 82 | /** 83 | * Converts an input geojson Point geometry to gml 84 | * @function 85 | * @param {Number[]} coords the coordinates member of the geojson geometry 86 | * @param {String|Number} gmlId the gml:id 87 | * @param {Params} params optional parameters 88 | * @returns {String} a string containing gml representing the input geometry 89 | */ 90 | 91 | 92 | function point(coords, gmlId, params = {}) { 93 | var { 94 | srsName: srsName, 95 | srsDimension: srsDimension 96 | } = params; 97 | return `` + `` + orderCoords(coords).join(' ') + '' + ''; 103 | } 104 | /** 105 | * Converts an input geojson LineString geometry to gml 106 | * @function 107 | * @param {Number[][]} coords the coordinates member of the geojson geometry 108 | * @param {String|Number} gmlId the gml:id 109 | * @param {Params} params optional parameters 110 | * @returns {String} a string containing gml representing the input geometry 111 | */ 112 | 113 | function lineString(coords, gmlId, params = {}) { 114 | var { 115 | srsName: srsName, 116 | srsDimension: srsDimension 117 | } = params; 118 | return `` + `` + coords.map(e => orderCoords(e).join(' ')).join(' ') + '' + ''; 124 | } 125 | /** 126 | * Converts an input geojson LinearRing member of a polygon geometry to gml 127 | * @function 128 | * @param {Number[][]} coords the coordinates member of the geojson geometry 129 | * @param {String|Number} gmlId the gml:id 130 | * @param {Params} params optional parameters 131 | * @returns {String} a string containing gml representing the input geometry 132 | */ 133 | 134 | function linearRing(coords, gmlId, params = {}) { 135 | var { 136 | srsName: srsName, 137 | srsDimension: srsDimension 138 | } = params; 139 | return `` + `` + coords.map(e => orderCoords(e).join(' ')).join(' ') + '' + ''; 145 | } 146 | /** 147 | * Converts an input geojson Polygon geometry to gml 148 | * @function 149 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 150 | * @param {String|Number} gmlId the gml:id 151 | * @param {Params} params optional parameters 152 | * @returns {String} a string containing gml representing the input geometry 153 | */ 154 | 155 | function polygon(coords, gmlId, params = {}) { 156 | // geom.coordinates are arrays of LinearRings 157 | var { 158 | srsName 159 | } = params; 160 | let polygon = `` + '' + linearRing(coords[0]) + ''; 164 | 165 | if (coords.length >= 2) { 166 | for (let ring of coords.slice(1)) { 167 | polygon += '' + linearRing(ring) + ''; 168 | } 169 | } 170 | 171 | polygon += ''; 172 | return polygon; 173 | } 174 | /** 175 | * Converts an input geojson MultiPoint geometry to gml 176 | * @function 177 | * @param {Number[][]} coords the coordinates member of the geojson geometry 178 | * @param {String|Number} gmlId the gml:id 179 | * @param {Params} params optional parameters 180 | * @returns {String} a string containing gml representing the input geometry 181 | */ 182 | 183 | function multiPoint(coords, gmlId, params = {}) { 184 | return multi('MultiPoint', 'pointMembers', point, coords, gmlId, params); 185 | } 186 | /** 187 | * Converts an input geojson MultiLineString geometry to gml 188 | * @function 189 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 190 | * @param {String|Number} gmlId the gml:id 191 | * @param {Params} params optional parameters 192 | * @returns {String} a string containing gml representing the input geometry 193 | */ 194 | 195 | function multiLineString(coords, gmlId, params = {}) { 196 | return multi('MultiCurve', 'curveMembers', lineString, coords, gmlId, params); 197 | } 198 | /** 199 | * Converts an input geojson MultiPolygon geometry to gml 200 | * @function 201 | * @param {Number[][][][]} coords the coordinates member of the geojson geometry 202 | * @param {String|Number} gmlId the gml:id 203 | * @param {Params} params optional parameters 204 | * @returns {String} a string containing gml representing the input geometry 205 | */ 206 | 207 | function multiPolygon(coords, gmlId, params = {}) { 208 | return multi('MultiSurface', 'surfaceMembers', polygon, coords, gmlId, params); 209 | } 210 | /** 211 | * A helper to de-camelcase this module's geometry conversion methods 212 | * @param {Object} obj a mapping of camelcase geometry types to converter functions 213 | * @return {Object} a mapping of capitalized geometry types to converter functions 214 | * @example 215 | * makeConverter({lineString}) 216 | * // returns {LineString: lineString} 217 | */ 218 | 219 | function makeConverter(obj) { 220 | return Object.entries(obj).map(([type, converter]) => { 221 | return { 222 | [type[0].toUpperCase() + type.slice(1)]: converter 223 | }; 224 | }).reduce((a, b) => Object.assign(a, b), {}); 225 | } 226 | /** 227 | * A helper to map geometry types to converter functions. 228 | * @function 229 | * @param {Object} obj an object mapping camelcase-d geometry type names to 230 | * converter functions for that type. 231 | * @example 232 | * import {point, lineString} from 'geojson-to-gml-3'; 233 | * const geomToGml = makeTranslator({point, lineString}); 234 | * geomToGml({type: 'Point', coordinates: [0, 0]}); 235 | */ 236 | 237 | 238 | function makeTranslator(obj) { 239 | const converter = makeConverter(obj); 240 | return function (geom, gmlId, params) { 241 | const warn = () => new Error(`unkown: ${geom.type} ` + [...arguments].join()); 242 | 243 | const convert = converter[geom.type] || warn; 244 | return convert(geom.coordinates || geom.geometries, gmlId, params); 245 | }; 246 | } 247 | /** 248 | * a namespace to switch between geojson-handling functions by geojson.type 249 | * @const 250 | * @type {Object} 251 | */ 252 | 253 | const allTypes = makeConverter({ 254 | point, 255 | lineString, 256 | linearRing, 257 | polygon, 258 | multiPoint, 259 | multiLineString, 260 | multiPolygon 261 | }); 262 | /** 263 | * Converts an input geojson GeometryCollection geometry to gml 264 | * @function 265 | * @param {Object[]} coords the coordinates member of the geojson geometry 266 | * @param {String|Number} gmlId the gml:id 267 | * @param {Object} params optional parameters 268 | * @param {?String} params.srsName as string specifying SRS 269 | * @param {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 270 | * @returns {String} a string containing gml representing the input geometry 271 | */ 272 | 273 | function geometryCollection(geoms, gmlId, params = {}) { 274 | return multi('MultiGeometry', 'geometryMembers', allTypes, geoms, gmlId, params); 275 | } 276 | /** 277 | * Translates any geojson geometry into GML 3.2.1 278 | * @public 279 | * @function 280 | * @param {Object} geom a geojson geometry object 281 | * @param {?Array} geom.coordinates the nested array of coordinates forming the geometry 282 | * @param {?Object[]} geom.geometries for a GeometryCollection only, the array of member geometry objects 283 | * @param {String|Number} gmlId the gml:id of the geometry 284 | * @param {object} params optional parameters 285 | * @param {?String} params.srsName a string specifying the SRS 286 | * @param {?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 287 | * @param {?Number[]|?String[]} gmlIds an array of number/string gml:ids of the member geometries of a multigeometry. 288 | * @returns {String} a valid gml string describing the input geojson geometry 289 | */ 290 | 291 | const geomToGml = makeTranslator(Object.assign({ 292 | geometryCollection 293 | }, allTypes)); 294 | 295 | /** 296 | * common checks, coersions, and informative errors/ warnings 297 | * @module ensure 298 | */ 299 | 300 | /** 301 | * Ensures the result is an array. 302 | * @private 303 | * @function 304 | * @param {Feature|Feature[]|FeatureCollection} maybe a GeoJSON 305 | * FeatureCollection, Feature, or an array of Features. 306 | * @return {Feature[]} 307 | */ 308 | const array = (...maybe) => (maybe[0].features || [].concat(...maybe)).filter(Boolean); 309 | /** 310 | * Ensures a layer.id format of an input id. 311 | * @function 312 | * @param {String} lyr layer name 313 | * @param {String} id id, possibly already in correct layer.id format. 314 | * @return {String} a correctly-formatted gml:id 315 | */ 316 | 317 | const id = (lyr, id) => /\./.exec(id || '') ? id : `${lyr}.${id}`; 318 | /** 319 | * return a correctly-formatted typeName 320 | * @function 321 | * @param {String} ns namespace 322 | * @param {String} layer layer name 323 | * @param {String} typeName typeName to check 324 | * @return {String} a correctly-formatted typeName 325 | * @throws {Error} if typeName it cannot form a typeName from ns and layer 326 | */ 327 | 328 | const typeName = (ns, layer, typeName) => { 329 | if (!typeName && !(ns && layer)) { 330 | throw new Error(`no typename possible: ${JSON.stringify({ 331 | typeName, 332 | ns, 333 | layer 334 | }, null, 2)}`); 335 | } 336 | 337 | return typeName || `${layer}`; 338 | }; // http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286 339 | 340 | /** 341 | * xml utilities. 342 | * @module xml 343 | */ 344 | 345 | /** 346 | * Turns an object into a string of xml attribute key-value pairs. 347 | * @function 348 | * @param {Object} attrs an object mapping attribute names to attribute values 349 | * @return {String} a string of xml attribute key-value pairs 350 | */ 351 | 352 | function attrs$1(attrs) { 353 | return Object.keys(attrs).map(a => attrs[a] ? ` ${a}="${escape(attrs[a])}"` : '').join(''); 354 | } 355 | /** 356 | * Creates a string xml tag. 357 | * @function 358 | * @param {String} ns the tag's xml namespace abbreviation. 359 | * @param {String} tagName the tag name. 360 | * @param {Object} attrsObj @see xml.attrs. 361 | * @param {String} inner inner xml. 362 | * @return {String} an xml string. 363 | */ 364 | 365 | function tag(ns, tagName, attrsObj, inner) { 366 | let tag = (ns ? `${ns}:` : '') + tagName; 367 | 368 | if (tagName) { 369 | return `<${tag}${attrs$1(attrsObj)}${inner !== null ? `>${inner}`; 370 | } else { 371 | throw new Error('no tag supplied ' + JSON.stringify({ 372 | ns, 373 | tagName, 374 | attrsObj, 375 | inner 376 | }, null, 2)); 377 | } 378 | } 379 | /** 380 | * Shorthand for creating a wfs xml tag. 381 | * @param {String} tagName a valid wfs tag name. 382 | * @param {Object} attrsObj @see xml.attrs. 383 | * @param {String} inner @see xml.tag. 384 | * @return {String} a wfs element. 385 | */ 386 | 387 | const wfs = (tagName, attrsObj, inner) => tag('wfs', tagName, attrsObj, inner); 388 | /** 389 | * Creates a fes:ResourceId filter from a layername and id 390 | * @function 391 | * @param {String} lyr layer name of the filtered feature 392 | * @param {String} id feature id 393 | * @return {String} a filter-ecoding of the filter. 394 | */ 395 | 396 | const idFilter = (lyr, id$$1) => { 397 | return ``; 398 | }; 399 | /** 400 | * Creates an xml-safe string from a given input string 401 | * @function 402 | * @param {String} input String to escape 403 | * @return {String} XML-safe string 404 | */ 405 | 406 | function escape(input) { 407 | if (typeof input !== 'string') { 408 | // Backup check for non-strings 409 | return input; 410 | } 411 | 412 | const output = input.replace(/[<>&'"]/g, char => { 413 | switch (char) { 414 | case '<': 415 | return '<'; 416 | 417 | case '>': 418 | return '>'; 419 | 420 | case '&': 421 | return '&'; 422 | 423 | case `'`: 424 | return '''; 425 | 426 | case '"': 427 | return '"'; 428 | } 429 | }); 430 | return output; 431 | } 432 | 433 | /* eslint-disable camelcase */ 434 | 435 | /** 436 | * Common utilities for handling parameters for creation of WFS trasactions. 437 | * @module utils 438 | */ 439 | 440 | /** 441 | * Iterates over the key-value pairs, filtering by a whitelist if available. 442 | * @function 443 | * @param {Array} whitelist a whitelist of property names 444 | * @param {Object} properties an object mapping property names to values 445 | * @param {Function} cb a function to call on each (whitelisted key, value) pair 446 | */ 447 | 448 | const useWhitelistIfAvailable = (whitelist, properties, cb) => { 449 | for (let prop of whitelist || Object.keys(properties)) { 450 | const val = properties[prop]; 451 | 452 | if (Number.isNaN(val)) { 453 | throw new Error('NaN is not allowed.'); 454 | } 455 | 456 | if (val !== undefined) { 457 | cb(prop, val); 458 | } 459 | } 460 | }; 461 | const featureMembers = new Set(['properties', 'geometry', 'id', 'layer']); 462 | /** 463 | * Resolves attributes from feature, then params unless they are normally 464 | * found in the feature 465 | * @param {Object} feature a geojson feature 466 | * @param {Object} params an object of backup / override parameters 467 | * @param {Array} args parameter names to resolve from feature or 468 | * params 469 | * @return {Object} an object mapping each named parameter to its resolved 470 | * value 471 | */ 472 | 473 | function unpack(feature, params, ...args) { 474 | let results = {}; 475 | 476 | for (let arg of args) { 477 | if (arg === 'layer') { 478 | results[arg] = (params.layer || {}).id || params.layer || (feature.layer || {}).id || feature.layer || ''; 479 | } else if (!featureMembers.has(arg)) { 480 | results[arg] = feature[arg] || params[arg] || ''; 481 | } else { 482 | results[arg] = params[arg] || feature[arg] || ''; 483 | } 484 | } 485 | 486 | return results; 487 | } 488 | /** 489 | * Generates an object to be passed to @see xml.attrs xmlns:ns="uri" definitions 490 | * for a wfs:Transaction 491 | * @param {Object} nsAssignments @see Params.nsAssignments 492 | * @param {String} xml arbitrary xml. 493 | * @return {Object} an object mapping each ns to its URI as 'xmlns:ns' : 'URI'. 494 | * @throws {Error} if any namespace used within `xml` is missing a URI 495 | * definition 496 | */ 497 | 498 | function generateNsAssignments(nsAssignments, xml) { 499 | let attrs = {}; 500 | 501 | const makeNsAssignment = (ns, uri) => attrs[`xmlns:${ns}`] = uri; 502 | 503 | Object.keys(nsAssignments).forEach(ns => { 504 | makeNsAssignment(ns, nsAssignments[ns]); 505 | }); // check all ns's assigned 506 | 507 | let re = /(<|typeName=")(\w+):/g; 508 | let arr; 509 | let allNamespaces = new Set(); 510 | 511 | while ((arr = re.exec(xml)) !== null) { 512 | allNamespaces.add(arr[2]); 513 | } 514 | 515 | if (allNamespaces.has('fes')) { 516 | makeNsAssignment('fes', 'http://www.opengis.net/fes/2.0'); 517 | } 518 | makeNsAssignment('xsi', 'http://www.w3.org/2001/XMLSchema-instance'); 519 | makeNsAssignment('gml', 'http://www.opengis.net/gml/3.2'); 520 | makeNsAssignment('wfs', 'http://www.opengis.net/wfs/2.0'); 521 | 522 | for (let ns of allNamespaces) { 523 | if (!attrs['xmlns:' + ns]) { 524 | throw new Error(`unassigned namespace ${ns}`); 525 | } 526 | } 527 | /* , schemaLocations*/ 528 | 529 | 530 | return attrs; 531 | } 532 | /** 533 | * Returns a string alternating uri, whitespace, and the uri's schema's 534 | * location. 535 | * @param {Object} schemaLocations an object mapping uri:schemalocation 536 | * @return {string} a string that is a valid xsi:schemaLocation value. 537 | */ 538 | 539 | function generateSchemaLines(schemaLocations = {}) { 540 | // TODO: add ns assignment check 541 | schemaLocations['http://www.opengis.net/wfs/2.0'] = 'http://schemas.opengis.net/wfs/2.0/wfs.xsd'; 542 | let schemaLines = []; 543 | Object.entries(schemaLocations).forEach(entry => schemaLines.push(entry.join('\n'))); 544 | return schemaLines.join('\n'); 545 | } 546 | /** 547 | * Turns an array of geojson features into gml:_feature strings describing them. 548 | * @function 549 | * @param {Feature[]} features an array of features to translate to 550 | * gml:_features. 551 | * @param {Params} params an object of backup / override parameters 552 | * @return {String} a gml:_feature string. 553 | */ 554 | 555 | function translateFeatures(features, params = {}) { 556 | let inner = ''; 557 | let { 558 | srsName, 559 | srsDimension 560 | } = params; 561 | 562 | for (let feature of features) { 563 | // TODO: add whitelist support 564 | let { 565 | ns, 566 | layer, 567 | geometry_name, 568 | properties, 569 | id: id$$1, 570 | whitelist 571 | } = unpack(feature, params, 'ns', 'layer', 'geometry_name', 'properties', 'id', 'whitelist'); 572 | let fields = ''; 573 | 574 | if (geometry_name) { 575 | fields += tag(ns, geometry_name, {}, geomToGml(feature.geometry, '', { 576 | srsName, 577 | srsDimension 578 | })); 579 | } 580 | 581 | useWhitelistIfAvailable(whitelist, properties, (prop, val) => { 582 | if (val === null) { 583 | return fields; 584 | } 585 | 586 | return fields += tag(ns, prop, {}, escape(properties[prop])); 587 | }); 588 | inner += tag(ns, layer, { 589 | 'gml:id': id(layer, id$$1) 590 | }, fields); 591 | } 592 | 593 | return inner; 594 | } 595 | 596 | /** 597 | * Builds a filter from feature ids if one is not already input. 598 | * @function 599 | * @param {?String} filter a possible string filter 600 | * @param {Array} features an array of geojson feature objects 601 | * @param {Object} params an object of backup / override parameters 602 | * @return {String} A filter, or the input filter if it was a string. 603 | */ 604 | 605 | function filter(filter, features, params) { 606 | if (!filter) { 607 | filter = ''; 608 | 609 | for (let feature of features) { 610 | let layer = unpack(feature, params); 611 | filter += idFilter(layer, feature.id); 612 | } 613 | 614 | return `${filter}`; 615 | } else { 616 | return filter; 617 | } 618 | } 619 | 620 | /* eslint-disable camelcase, new-cap */ 621 | /** 622 | * Returns a wfs:Insert tag wrapping a translated feature 623 | * @function 624 | * @param {Feature[]|FeatureCollection|Feature} features Feature(s) to pass to 625 | * @see translateFeatures 626 | * @param {Params} params to be passed to @see translateFeatures, with optional 627 | * inputFormat, srsName, handle for the wfs:Insert tag. 628 | * @return {string} a wfs:Insert string. 629 | */ 630 | 631 | function Insert(features, params = {}) { 632 | features = array(features); 633 | let { 634 | inputFormat, 635 | srsName, 636 | handle 637 | } = params; 638 | 639 | if (!features.length) { 640 | console.warn('no features supplied'); 641 | return ''; 642 | } 643 | 644 | let toInsert = translateFeatures(features, params); 645 | return tag('wfs', 'Insert', { 646 | inputFormat, 647 | srsName, 648 | handle 649 | }, toInsert); 650 | } 651 | /** 652 | * Updates the input features in bulk with params.properties or by id. 653 | * @param {Feature[]|FeatureCollection} features features to update. These may 654 | * pass in geometry_name, properties, and layer (overruled by params) and 655 | * ns, layer, srsName (overruling params). 656 | * @param {Params} params with optional properties, ns (namespace), layer, 657 | * geometry_name, filter, typeName, whitelist. 658 | * @return {string} a string wfs:Upate action. 659 | */ 660 | 661 | function Update(features, params = {}) { 662 | features = array(features); 663 | /** 664 | * makes a wfs:Property string containg a wfs:ValueReference, wfs:Value pair. 665 | * @private 666 | * @function 667 | * @memberof Update~ 668 | * @param {string} prop the field/property name 669 | * @param {string} val the field/property value 670 | * @param {string} action one of 'insertBefore', 'insertAfter', 'remove', 671 | * 'replace'. See [OGC 09-025r2 § 15.2.5.2.1]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286}. 672 | * `action` would delete or modify the order of fields within the remote 673 | * feature. There is currently no way to input `action,` since wfs:Update's 674 | * default action, 'replace', is sufficient. 675 | * @return {string} a wfs:Property(wfs:ValueReference) pair. 676 | */ 677 | 678 | const makeKvp = (prop, val, action$$1) => { 679 | let value = ''; 680 | 681 | if (val === null) { 682 | value = wfs('Value', { 683 | 'xsi:nil': true 684 | }, ''); 685 | } else if (val !== undefined) { 686 | value = wfs('Value', {}, val); 687 | } 688 | 689 | return wfs('Property', {}, wfs('ValueReference', { 690 | action: action$$1 691 | }, prop) + value); 692 | }; 693 | 694 | if (params.properties) { 695 | let { 696 | /* handle, */ 697 | inputFormat, 698 | filter: filter$$1, 699 | typeName: typeName$$1, 700 | whitelist 701 | } = params; 702 | let { 703 | srsName, 704 | ns, 705 | layer, 706 | geometry_name 707 | } = unpack(features[0] || {}, params, 'srsName', 'ns', 'layer', 'geometry_name'); 708 | typeName$$1 = typeName(ns, layer, typeName$$1); 709 | filter$$1 = filter(filter$$1, features, params); 710 | 711 | if (!filter$$1 && !features.length) { 712 | console.warn('neither features nor filter supplied'); 713 | return ''; 714 | } 715 | 716 | let fields = ''; 717 | useWhitelistIfAvailable( // TODO: action attr 718 | whitelist, params.properties, (k, v) => fields += makeKvp(k, escape(v))); 719 | 720 | if (geometry_name) { 721 | fields += makeKvp(geometry_name, tag(ns, geometry_name, {}, geomToGml(params.geometry, '', { 722 | srsName 723 | }))); 724 | } 725 | 726 | return wfs('Update', { 727 | inputFormat, 728 | srsName, 729 | typeName: typeName$$1 730 | }, fields + filter$$1); 731 | } else { 732 | // encapsulate each update in its own Update tag 733 | return features.map(f => Update(f, Object.assign({}, params, { 734 | properties: f.properties 735 | }))).join(''); 736 | } 737 | } 738 | /** 739 | * Creates a wfs:Delete action, creating a filter and typeName from feature ids 740 | * if none are supplied. 741 | * @param {Feature[]|FeatureCollection|Feature} features 742 | * @param {Params} params optional parameter overrides. 743 | * @param {string} [params.ns] @see Params.ns 744 | * @param {string|Object} [params.layer] @see Params.layer 745 | * @param {string} [params.typeName] @see Params.typeName. This will be inferred 746 | * from feature/params layer and ns if this is left undefined. 747 | * @param {filter} [params.filter] @see Params.filter. This will be inferred 748 | * from feature ids and layer(s) if left undefined (@see ensureFilter). 749 | * @return {string} a wfs:Delete string. 750 | */ 751 | 752 | function Delete(features, params = {}) { 753 | features = array(features); 754 | let { 755 | filter: filter$$1, 756 | typeName: typeName$$1 757 | } = params; // TODO: recur & encapsulate by typeName 758 | 759 | let { 760 | ns, 761 | layer 762 | } = unpack(features[0] || {}, params, 'layer', 'ns'); 763 | typeName$$1 = typeName(ns, layer, typeName$$1); 764 | filter$$1 = filter(filter$$1, features, params); 765 | return wfs('Delete', { 766 | typeName: typeName$$1 767 | }, filter$$1); 768 | } 769 | /** 770 | * Returns a string wfs:Replace action. 771 | * @param {Feature[]|FeatureCollection|Feature} features feature(s) to replace 772 | * @param {Params} params with optional filter, inputFormat, srsName 773 | * @return {string} a string wfs:Replace action. 774 | */ 775 | 776 | function Replace(features, params = {}) { 777 | features = array(features); 778 | let { 779 | filter: filter$$1, 780 | inputFormat, 781 | srsName 782 | } = unpack(features[0] || {}, params || {}, 'filter', 'inputFormat', 'srsName'); 783 | let replacements = translateFeatures([features[0]].filter(f => f), params || { 784 | srsName 785 | }); 786 | filter$$1 = filter(filter$$1, features, params); 787 | return wfs('Replace', { 788 | inputFormat, 789 | srsName 790 | }, replacements + filter$$1); 791 | } 792 | /** 793 | * Wraps the input actions in a wfs:Transaction. 794 | * @param {Object|string[]|string} actions an object mapping {Insert, Update, 795 | * Delete} to feature(s) to pass to Insert, Update, Delete, or wfs:action 796 | * string(s) to wrap in a transaction. 797 | * @param {TransactionParams} params optional srsName, lockId, releaseAction, 798 | * handle, inputFormat, version, and required nsAssignments, schemaLocations. 799 | * @return {string} A wfs:transaction wrapping the input actions. 800 | * @throws {Error} if `actions` is not an array of strings, a string, or 801 | * {@see Insert, @see Update, @see Delete}, where each action are valid inputs 802 | * to the eponymous function. 803 | */ 804 | 805 | function Transaction(actions, params = {}) { 806 | const transactionParams = ['srsName', 'lockId', 'releaseAction', 'handle']; 807 | let { 808 | version, 809 | // optional 810 | nsAssignments = {} // required 811 | 812 | } = params; // let converter = {Insert, Update, Delete}; 813 | 814 | let { 815 | insert: toInsert, 816 | update: toUpdate, 817 | delete: toDelete 818 | } = actions || {}; 819 | let finalActions = ''; // processedActions would be more accurate 820 | 821 | if (Array.isArray(actions) && actions.every(v => typeof v == 'string')) { 822 | finalActions += actions.join(''); 823 | } else if (typeof actions == 'string') { 824 | finalActions = actions; 825 | } else if ([toInsert, toUpdate, toDelete].some(e => e)) { 826 | finalActions += Insert(toInsert, params) + Update(toUpdate, params) + Delete(toDelete, params); 827 | } else { 828 | throw new Error(`unexpected input: ${JSON.stringify(actions)}`); 829 | } // generate schemaLocation, xmlns's 830 | 831 | 832 | let attrs = generateNsAssignments(nsAssignments, actions); 833 | attrs['xsi:schemaLocation'] = generateSchemaLines(params.schemaLocations); 834 | attrs['service'] = 'WFS'; 835 | attrs['version'] = /2\.0\.\d+/.exec(version || '') ? version : '2.0.0'; 836 | transactionParams.forEach(param => { 837 | if (params[param]) { 838 | attrs[param] = params[param]; 839 | } 840 | }); 841 | return wfs('Transaction', attrs, finalActions); 842 | } 843 | 844 | exports.Insert = Insert; 845 | exports.Update = Update; 846 | exports.Delete = Delete; 847 | exports.Replace = Replace; 848 | exports.Transaction = Transaction; 849 | -------------------------------------------------------------------------------- /dist/es6.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (global = global || self, factory(global.geojsonToWfst = {})); 5 | }(this, function (exports) { 'use strict'; 6 | 7 | /* eslint-disable no-console */ 8 | /** 9 | * reorder coordinates to lat, lng iff config.coordinateOrder is false. 10 | * @param {Number[]} coords An array of coordinates, [lng, lat, ...etc] 11 | * @return {Number[]} An array of coordinates in the correct order. 12 | */ 13 | 14 | function orderCoords(coords) { 15 | { 16 | return coords; 17 | } 18 | 19 | if (coords[2]) { 20 | return [coords[1], coords[0], coords[2]]; 21 | } 22 | 23 | return coords.reverse(); 24 | } 25 | /** @private*/ 26 | 27 | 28 | function attrs(attrMappings) { 29 | // return Object.entries() 30 | let results = ''; 31 | 32 | for (let attrName in attrMappings) { 33 | let value = attrMappings[attrName]; 34 | results += value ? ` ${attrName}="${value}"` : ''; 35 | } 36 | 37 | return results; 38 | } 39 | /** 40 | * Optional parameters for conversion of geojson to gml geometries 41 | * @typedef {Object} Params 42 | * @property {?String} params.srsName as string specifying SRS, e.g. 'EPSG:4326'. Only applies to multigeometries. 43 | * @property {?Number[]|?String[]} params.gmlIds an array of number/string gml:ids of the member geometries. 44 | * @property {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 45 | */ 46 | 47 | /** 48 | * A handler to compile geometries to multigeometries 49 | * @function 50 | * @param {String} name the name of the target multigeometry 51 | * @param {String} memberName the gml:tag of each multigeometry member. 52 | * @param {Object[]|Array} geom an array of geojson geometries 53 | * @param {String|Number} gmlId the gml:id of the multigeometry 54 | * @param {Params} params optional parameters. Omit gmlIds at your own risk, however. 55 | * @returns {String} a string containing gml describing the input multigeometry 56 | * @throws {Error} if a member geometry cannot be converted to gml 57 | */ 58 | 59 | 60 | function multi(name, memberName, membercb, geom, gmlId, params = {}) { 61 | var { 62 | srsName, 63 | gmlIds 64 | } = params; 65 | let multi = ``; 69 | multi += ``; 70 | geom.forEach(function (member, i) { 71 | let _gmlId = member.id || (gmlIds || [])[i] || ''; 72 | 73 | if (name == 'MultiGeometry') { 74 | let memberType = member.type; 75 | member = member.coordinates; 76 | multi += membercb[memberType](member, _gmlId, params); 77 | } else { 78 | multi += membercb(member, _gmlId, params); 79 | } 80 | }); 81 | multi += ``; 82 | return multi + ``; 83 | } 84 | /** 85 | * Converts an input geojson Point geometry to gml 86 | * @function 87 | * @param {Number[]} coords the coordinates member of the geojson geometry 88 | * @param {String|Number} gmlId the gml:id 89 | * @param {Params} params optional parameters 90 | * @returns {String} a string containing gml representing the input geometry 91 | */ 92 | 93 | 94 | function point(coords, gmlId, params = {}) { 95 | var { 96 | srsName: srsName, 97 | srsDimension: srsDimension 98 | } = params; 99 | return `` + `` + orderCoords(coords).join(' ') + '' + ''; 105 | } 106 | /** 107 | * Converts an input geojson LineString geometry to gml 108 | * @function 109 | * @param {Number[][]} coords the coordinates member of the geojson geometry 110 | * @param {String|Number} gmlId the gml:id 111 | * @param {Params} params optional parameters 112 | * @returns {String} a string containing gml representing the input geometry 113 | */ 114 | 115 | function lineString(coords, gmlId, params = {}) { 116 | var { 117 | srsName: srsName, 118 | srsDimension: srsDimension 119 | } = params; 120 | return `` + `` + coords.map(e => orderCoords(e).join(' ')).join(' ') + '' + ''; 126 | } 127 | /** 128 | * Converts an input geojson LinearRing member of a polygon geometry to gml 129 | * @function 130 | * @param {Number[][]} coords the coordinates member of the geojson geometry 131 | * @param {String|Number} gmlId the gml:id 132 | * @param {Params} params optional parameters 133 | * @returns {String} a string containing gml representing the input geometry 134 | */ 135 | 136 | function linearRing(coords, gmlId, params = {}) { 137 | var { 138 | srsName: srsName, 139 | srsDimension: srsDimension 140 | } = params; 141 | return `` + `` + coords.map(e => orderCoords(e).join(' ')).join(' ') + '' + ''; 147 | } 148 | /** 149 | * Converts an input geojson Polygon geometry to gml 150 | * @function 151 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 152 | * @param {String|Number} gmlId the gml:id 153 | * @param {Params} params optional parameters 154 | * @returns {String} a string containing gml representing the input geometry 155 | */ 156 | 157 | function polygon(coords, gmlId, params = {}) { 158 | // geom.coordinates are arrays of LinearRings 159 | var { 160 | srsName 161 | } = params; 162 | let polygon = `` + '' + linearRing(coords[0]) + ''; 166 | 167 | if (coords.length >= 2) { 168 | for (let ring of coords.slice(1)) { 169 | polygon += '' + linearRing(ring) + ''; 170 | } 171 | } 172 | 173 | polygon += ''; 174 | return polygon; 175 | } 176 | /** 177 | * Converts an input geojson MultiPoint geometry to gml 178 | * @function 179 | * @param {Number[][]} coords the coordinates member of the geojson geometry 180 | * @param {String|Number} gmlId the gml:id 181 | * @param {Params} params optional parameters 182 | * @returns {String} a string containing gml representing the input geometry 183 | */ 184 | 185 | function multiPoint(coords, gmlId, params = {}) { 186 | return multi('MultiPoint', 'pointMembers', point, coords, gmlId, params); 187 | } 188 | /** 189 | * Converts an input geojson MultiLineString geometry to gml 190 | * @function 191 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 192 | * @param {String|Number} gmlId the gml:id 193 | * @param {Params} params optional parameters 194 | * @returns {String} a string containing gml representing the input geometry 195 | */ 196 | 197 | function multiLineString(coords, gmlId, params = {}) { 198 | return multi('MultiCurve', 'curveMembers', lineString, coords, gmlId, params); 199 | } 200 | /** 201 | * Converts an input geojson MultiPolygon geometry to gml 202 | * @function 203 | * @param {Number[][][][]} coords the coordinates member of the geojson geometry 204 | * @param {String|Number} gmlId the gml:id 205 | * @param {Params} params optional parameters 206 | * @returns {String} a string containing gml representing the input geometry 207 | */ 208 | 209 | function multiPolygon(coords, gmlId, params = {}) { 210 | return multi('MultiSurface', 'surfaceMembers', polygon, coords, gmlId, params); 211 | } 212 | /** 213 | * A helper to de-camelcase this module's geometry conversion methods 214 | * @param {Object} obj a mapping of camelcase geometry types to converter functions 215 | * @return {Object} a mapping of capitalized geometry types to converter functions 216 | * @example 217 | * makeConverter({lineString}) 218 | * // returns {LineString: lineString} 219 | */ 220 | 221 | function makeConverter(obj) { 222 | return Object.entries(obj).map(([type, converter]) => { 223 | return { 224 | [type[0].toUpperCase() + type.slice(1)]: converter 225 | }; 226 | }).reduce((a, b) => Object.assign(a, b), {}); 227 | } 228 | /** 229 | * A helper to map geometry types to converter functions. 230 | * @function 231 | * @param {Object} obj an object mapping camelcase-d geometry type names to 232 | * converter functions for that type. 233 | * @example 234 | * import {point, lineString} from 'geojson-to-gml-3'; 235 | * const geomToGml = makeTranslator({point, lineString}); 236 | * geomToGml({type: 'Point', coordinates: [0, 0]}); 237 | */ 238 | 239 | 240 | function makeTranslator(obj) { 241 | const converter = makeConverter(obj); 242 | return function (geom, gmlId, params) { 243 | const warn = () => new Error(`unkown: ${geom.type} ` + [...arguments].join()); 244 | 245 | const convert = converter[geom.type] || warn; 246 | return convert(geom.coordinates || geom.geometries, gmlId, params); 247 | }; 248 | } 249 | /** 250 | * a namespace to switch between geojson-handling functions by geojson.type 251 | * @const 252 | * @type {Object} 253 | */ 254 | 255 | const allTypes = makeConverter({ 256 | point, 257 | lineString, 258 | linearRing, 259 | polygon, 260 | multiPoint, 261 | multiLineString, 262 | multiPolygon 263 | }); 264 | /** 265 | * Converts an input geojson GeometryCollection geometry to gml 266 | * @function 267 | * @param {Object[]} coords the coordinates member of the geojson geometry 268 | * @param {String|Number} gmlId the gml:id 269 | * @param {Object} params optional parameters 270 | * @param {?String} params.srsName as string specifying SRS 271 | * @param {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 272 | * @returns {String} a string containing gml representing the input geometry 273 | */ 274 | 275 | function geometryCollection(geoms, gmlId, params = {}) { 276 | return multi('MultiGeometry', 'geometryMembers', allTypes, geoms, gmlId, params); 277 | } 278 | /** 279 | * Translates any geojson geometry into GML 3.2.1 280 | * @public 281 | * @function 282 | * @param {Object} geom a geojson geometry object 283 | * @param {?Array} geom.coordinates the nested array of coordinates forming the geometry 284 | * @param {?Object[]} geom.geometries for a GeometryCollection only, the array of member geometry objects 285 | * @param {String|Number} gmlId the gml:id of the geometry 286 | * @param {object} params optional parameters 287 | * @param {?String} params.srsName a string specifying the SRS 288 | * @param {?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 289 | * @param {?Number[]|?String[]} gmlIds an array of number/string gml:ids of the member geometries of a multigeometry. 290 | * @returns {String} a valid gml string describing the input geojson geometry 291 | */ 292 | 293 | const geomToGml = makeTranslator(Object.assign({ 294 | geometryCollection 295 | }, allTypes)); 296 | 297 | /** 298 | * common checks, coersions, and informative errors/ warnings 299 | * @module ensure 300 | */ 301 | 302 | /** 303 | * Ensures the result is an array. 304 | * @private 305 | * @function 306 | * @param {Feature|Feature[]|FeatureCollection} maybe a GeoJSON 307 | * FeatureCollection, Feature, or an array of Features. 308 | * @return {Feature[]} 309 | */ 310 | const array = (...maybe) => (maybe[0].features || [].concat(...maybe)).filter(Boolean); 311 | /** 312 | * Ensures a layer.id format of an input id. 313 | * @function 314 | * @param {String} lyr layer name 315 | * @param {String} id id, possibly already in correct layer.id format. 316 | * @return {String} a correctly-formatted gml:id 317 | */ 318 | 319 | const id = (lyr, id) => /\./.exec(id || '') ? id : `${lyr}.${id}`; 320 | /** 321 | * return a correctly-formatted typeName 322 | * @function 323 | * @param {String} ns namespace 324 | * @param {String} layer layer name 325 | * @param {String} typeName typeName to check 326 | * @return {String} a correctly-formatted typeName 327 | * @throws {Error} if typeName it cannot form a typeName from ns and layer 328 | */ 329 | 330 | const typeName = (ns, layer, typeName) => { 331 | if (!typeName && !(ns && layer)) { 332 | throw new Error(`no typename possible: ${JSON.stringify({ 333 | typeName, 334 | ns, 335 | layer 336 | }, null, 2)}`); 337 | } 338 | 339 | return typeName || `${layer}`; 340 | }; // http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286 341 | 342 | /** 343 | * xml utilities. 344 | * @module xml 345 | */ 346 | 347 | /** 348 | * Turns an object into a string of xml attribute key-value pairs. 349 | * @function 350 | * @param {Object} attrs an object mapping attribute names to attribute values 351 | * @return {String} a string of xml attribute key-value pairs 352 | */ 353 | 354 | function attrs$1(attrs) { 355 | return Object.keys(attrs).map(a => attrs[a] ? ` ${a}="${escape(attrs[a])}"` : '').join(''); 356 | } 357 | /** 358 | * Creates a string xml tag. 359 | * @function 360 | * @param {String} ns the tag's xml namespace abbreviation. 361 | * @param {String} tagName the tag name. 362 | * @param {Object} attrsObj @see xml.attrs. 363 | * @param {String} inner inner xml. 364 | * @return {String} an xml string. 365 | */ 366 | 367 | function tag(ns, tagName, attrsObj, inner) { 368 | let tag = (ns ? `${ns}:` : '') + tagName; 369 | 370 | if (tagName) { 371 | return `<${tag}${attrs$1(attrsObj)}${inner !== null ? `>${inner}`; 372 | } else { 373 | throw new Error('no tag supplied ' + JSON.stringify({ 374 | ns, 375 | tagName, 376 | attrsObj, 377 | inner 378 | }, null, 2)); 379 | } 380 | } 381 | /** 382 | * Shorthand for creating a wfs xml tag. 383 | * @param {String} tagName a valid wfs tag name. 384 | * @param {Object} attrsObj @see xml.attrs. 385 | * @param {String} inner @see xml.tag. 386 | * @return {String} a wfs element. 387 | */ 388 | 389 | const wfs = (tagName, attrsObj, inner) => tag('wfs', tagName, attrsObj, inner); 390 | /** 391 | * Creates a fes:ResourceId filter from a layername and id 392 | * @function 393 | * @param {String} lyr layer name of the filtered feature 394 | * @param {String} id feature id 395 | * @return {String} a filter-ecoding of the filter. 396 | */ 397 | 398 | const idFilter = (lyr, id$$1) => { 399 | return ``; 400 | }; 401 | /** 402 | * Creates an xml-safe string from a given input string 403 | * @function 404 | * @param {String} input String to escape 405 | * @return {String} XML-safe string 406 | */ 407 | 408 | function escape(input) { 409 | if (typeof input !== 'string') { 410 | // Backup check for non-strings 411 | return input; 412 | } 413 | 414 | const output = input.replace(/[<>&'"]/g, char => { 415 | switch (char) { 416 | case '<': 417 | return '<'; 418 | 419 | case '>': 420 | return '>'; 421 | 422 | case '&': 423 | return '&'; 424 | 425 | case `'`: 426 | return '''; 427 | 428 | case '"': 429 | return '"'; 430 | } 431 | }); 432 | return output; 433 | } 434 | 435 | /* eslint-disable camelcase */ 436 | 437 | /** 438 | * Common utilities for handling parameters for creation of WFS trasactions. 439 | * @module utils 440 | */ 441 | 442 | /** 443 | * Iterates over the key-value pairs, filtering by a whitelist if available. 444 | * @function 445 | * @param {Array} whitelist a whitelist of property names 446 | * @param {Object} properties an object mapping property names to values 447 | * @param {Function} cb a function to call on each (whitelisted key, value) pair 448 | */ 449 | 450 | const useWhitelistIfAvailable = (whitelist, properties, cb) => { 451 | for (let prop of whitelist || Object.keys(properties)) { 452 | const val = properties[prop]; 453 | 454 | if (Number.isNaN(val)) { 455 | throw new Error('NaN is not allowed.'); 456 | } 457 | 458 | if (val !== undefined) { 459 | cb(prop, val); 460 | } 461 | } 462 | }; 463 | const featureMembers = new Set(['properties', 'geometry', 'id', 'layer']); 464 | /** 465 | * Resolves attributes from feature, then params unless they are normally 466 | * found in the feature 467 | * @param {Object} feature a geojson feature 468 | * @param {Object} params an object of backup / override parameters 469 | * @param {Array} args parameter names to resolve from feature or 470 | * params 471 | * @return {Object} an object mapping each named parameter to its resolved 472 | * value 473 | */ 474 | 475 | function unpack(feature, params, ...args) { 476 | let results = {}; 477 | 478 | for (let arg of args) { 479 | if (arg === 'layer') { 480 | results[arg] = (params.layer || {}).id || params.layer || (feature.layer || {}).id || feature.layer || ''; 481 | } else if (!featureMembers.has(arg)) { 482 | results[arg] = feature[arg] || params[arg] || ''; 483 | } else { 484 | results[arg] = params[arg] || feature[arg] || ''; 485 | } 486 | } 487 | 488 | return results; 489 | } 490 | /** 491 | * Generates an object to be passed to @see xml.attrs xmlns:ns="uri" definitions 492 | * for a wfs:Transaction 493 | * @param {Object} nsAssignments @see Params.nsAssignments 494 | * @param {String} xml arbitrary xml. 495 | * @return {Object} an object mapping each ns to its URI as 'xmlns:ns' : 'URI'. 496 | * @throws {Error} if any namespace used within `xml` is missing a URI 497 | * definition 498 | */ 499 | 500 | function generateNsAssignments(nsAssignments, xml) { 501 | let attrs = {}; 502 | 503 | const makeNsAssignment = (ns, uri) => attrs[`xmlns:${ns}`] = uri; 504 | 505 | Object.keys(nsAssignments).forEach(ns => { 506 | makeNsAssignment(ns, nsAssignments[ns]); 507 | }); // check all ns's assigned 508 | 509 | let re = /(<|typeName=")(\w+):/g; 510 | let arr; 511 | let allNamespaces = new Set(); 512 | 513 | while ((arr = re.exec(xml)) !== null) { 514 | allNamespaces.add(arr[2]); 515 | } 516 | 517 | if (allNamespaces.has('fes')) { 518 | makeNsAssignment('fes', 'http://www.opengis.net/fes/2.0'); 519 | } 520 | makeNsAssignment('xsi', 'http://www.w3.org/2001/XMLSchema-instance'); 521 | makeNsAssignment('gml', 'http://www.opengis.net/gml/3.2'); 522 | makeNsAssignment('wfs', 'http://www.opengis.net/wfs/2.0'); 523 | 524 | for (let ns of allNamespaces) { 525 | if (!attrs['xmlns:' + ns]) { 526 | throw new Error(`unassigned namespace ${ns}`); 527 | } 528 | } 529 | /* , schemaLocations*/ 530 | 531 | 532 | return attrs; 533 | } 534 | /** 535 | * Returns a string alternating uri, whitespace, and the uri's schema's 536 | * location. 537 | * @param {Object} schemaLocations an object mapping uri:schemalocation 538 | * @return {string} a string that is a valid xsi:schemaLocation value. 539 | */ 540 | 541 | function generateSchemaLines(schemaLocations = {}) { 542 | // TODO: add ns assignment check 543 | schemaLocations['http://www.opengis.net/wfs/2.0'] = 'http://schemas.opengis.net/wfs/2.0/wfs.xsd'; 544 | let schemaLines = []; 545 | Object.entries(schemaLocations).forEach(entry => schemaLines.push(entry.join('\n'))); 546 | return schemaLines.join('\n'); 547 | } 548 | /** 549 | * Turns an array of geojson features into gml:_feature strings describing them. 550 | * @function 551 | * @param {Feature[]} features an array of features to translate to 552 | * gml:_features. 553 | * @param {Params} params an object of backup / override parameters 554 | * @return {String} a gml:_feature string. 555 | */ 556 | 557 | function translateFeatures(features, params = {}) { 558 | let inner = ''; 559 | let { 560 | srsName, 561 | srsDimension 562 | } = params; 563 | 564 | for (let feature of features) { 565 | // TODO: add whitelist support 566 | let { 567 | ns, 568 | layer, 569 | geometry_name, 570 | properties, 571 | id: id$$1, 572 | whitelist 573 | } = unpack(feature, params, 'ns', 'layer', 'geometry_name', 'properties', 'id', 'whitelist'); 574 | let fields = ''; 575 | 576 | if (geometry_name) { 577 | fields += tag(ns, geometry_name, {}, geomToGml(feature.geometry, '', { 578 | srsName, 579 | srsDimension 580 | })); 581 | } 582 | 583 | useWhitelistIfAvailable(whitelist, properties, (prop, val) => { 584 | if (val === null) { 585 | return fields; 586 | } 587 | 588 | return fields += tag(ns, prop, {}, escape(properties[prop])); 589 | }); 590 | inner += tag(ns, layer, { 591 | 'gml:id': id(layer, id$$1) 592 | }, fields); 593 | } 594 | 595 | return inner; 596 | } 597 | 598 | /** 599 | * Builds a filter from feature ids if one is not already input. 600 | * @function 601 | * @param {?String} filter a possible string filter 602 | * @param {Array} features an array of geojson feature objects 603 | * @param {Object} params an object of backup / override parameters 604 | * @return {String} A filter, or the input filter if it was a string. 605 | */ 606 | 607 | function filter(filter, features, params) { 608 | if (!filter) { 609 | filter = ''; 610 | 611 | for (let feature of features) { 612 | let layer = unpack(feature, params); 613 | filter += idFilter(layer, feature.id); 614 | } 615 | 616 | return `${filter}`; 617 | } else { 618 | return filter; 619 | } 620 | } 621 | 622 | /* eslint-disable camelcase, new-cap */ 623 | /** 624 | * Returns a wfs:Insert tag wrapping a translated feature 625 | * @function 626 | * @param {Feature[]|FeatureCollection|Feature} features Feature(s) to pass to 627 | * @see translateFeatures 628 | * @param {Params} params to be passed to @see translateFeatures, with optional 629 | * inputFormat, srsName, handle for the wfs:Insert tag. 630 | * @return {string} a wfs:Insert string. 631 | */ 632 | 633 | function Insert(features, params = {}) { 634 | features = array(features); 635 | let { 636 | inputFormat, 637 | srsName, 638 | handle 639 | } = params; 640 | 641 | if (!features.length) { 642 | console.warn('no features supplied'); 643 | return ''; 644 | } 645 | 646 | let toInsert = translateFeatures(features, params); 647 | return tag('wfs', 'Insert', { 648 | inputFormat, 649 | srsName, 650 | handle 651 | }, toInsert); 652 | } 653 | /** 654 | * Updates the input features in bulk with params.properties or by id. 655 | * @param {Feature[]|FeatureCollection} features features to update. These may 656 | * pass in geometry_name, properties, and layer (overruled by params) and 657 | * ns, layer, srsName (overruling params). 658 | * @param {Params} params with optional properties, ns (namespace), layer, 659 | * geometry_name, filter, typeName, whitelist. 660 | * @return {string} a string wfs:Upate action. 661 | */ 662 | 663 | function Update(features, params = {}) { 664 | features = array(features); 665 | /** 666 | * makes a wfs:Property string containg a wfs:ValueReference, wfs:Value pair. 667 | * @private 668 | * @function 669 | * @memberof Update~ 670 | * @param {string} prop the field/property name 671 | * @param {string} val the field/property value 672 | * @param {string} action one of 'insertBefore', 'insertAfter', 'remove', 673 | * 'replace'. See [OGC 09-025r2 § 15.2.5.2.1]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286}. 674 | * `action` would delete or modify the order of fields within the remote 675 | * feature. There is currently no way to input `action,` since wfs:Update's 676 | * default action, 'replace', is sufficient. 677 | * @return {string} a wfs:Property(wfs:ValueReference) pair. 678 | */ 679 | 680 | const makeKvp = (prop, val, action$$1) => { 681 | let value = ''; 682 | 683 | if (val === null) { 684 | value = wfs('Value', { 685 | 'xsi:nil': true 686 | }, ''); 687 | } else if (val !== undefined) { 688 | value = wfs('Value', {}, val); 689 | } 690 | 691 | return wfs('Property', {}, wfs('ValueReference', { 692 | action: action$$1 693 | }, prop) + value); 694 | }; 695 | 696 | if (params.properties) { 697 | let { 698 | /* handle, */ 699 | inputFormat, 700 | filter: filter$$1, 701 | typeName: typeName$$1, 702 | whitelist 703 | } = params; 704 | let { 705 | srsName, 706 | ns, 707 | layer, 708 | geometry_name 709 | } = unpack(features[0] || {}, params, 'srsName', 'ns', 'layer', 'geometry_name'); 710 | typeName$$1 = typeName(ns, layer, typeName$$1); 711 | filter$$1 = filter(filter$$1, features, params); 712 | 713 | if (!filter$$1 && !features.length) { 714 | console.warn('neither features nor filter supplied'); 715 | return ''; 716 | } 717 | 718 | let fields = ''; 719 | useWhitelistIfAvailable( // TODO: action attr 720 | whitelist, params.properties, (k, v) => fields += makeKvp(k, escape(v))); 721 | 722 | if (geometry_name) { 723 | fields += makeKvp(geometry_name, tag(ns, geometry_name, {}, geomToGml(params.geometry, '', { 724 | srsName 725 | }))); 726 | } 727 | 728 | return wfs('Update', { 729 | inputFormat, 730 | srsName, 731 | typeName: typeName$$1 732 | }, fields + filter$$1); 733 | } else { 734 | // encapsulate each update in its own Update tag 735 | return features.map(f => Update(f, Object.assign({}, params, { 736 | properties: f.properties 737 | }))).join(''); 738 | } 739 | } 740 | /** 741 | * Creates a wfs:Delete action, creating a filter and typeName from feature ids 742 | * if none are supplied. 743 | * @param {Feature[]|FeatureCollection|Feature} features 744 | * @param {Params} params optional parameter overrides. 745 | * @param {string} [params.ns] @see Params.ns 746 | * @param {string|Object} [params.layer] @see Params.layer 747 | * @param {string} [params.typeName] @see Params.typeName. This will be inferred 748 | * from feature/params layer and ns if this is left undefined. 749 | * @param {filter} [params.filter] @see Params.filter. This will be inferred 750 | * from feature ids and layer(s) if left undefined (@see ensureFilter). 751 | * @return {string} a wfs:Delete string. 752 | */ 753 | 754 | function Delete(features, params = {}) { 755 | features = array(features); 756 | let { 757 | filter: filter$$1, 758 | typeName: typeName$$1 759 | } = params; // TODO: recur & encapsulate by typeName 760 | 761 | let { 762 | ns, 763 | layer 764 | } = unpack(features[0] || {}, params, 'layer', 'ns'); 765 | typeName$$1 = typeName(ns, layer, typeName$$1); 766 | filter$$1 = filter(filter$$1, features, params); 767 | return wfs('Delete', { 768 | typeName: typeName$$1 769 | }, filter$$1); 770 | } 771 | /** 772 | * Returns a string wfs:Replace action. 773 | * @param {Feature[]|FeatureCollection|Feature} features feature(s) to replace 774 | * @param {Params} params with optional filter, inputFormat, srsName 775 | * @return {string} a string wfs:Replace action. 776 | */ 777 | 778 | function Replace(features, params = {}) { 779 | features = array(features); 780 | let { 781 | filter: filter$$1, 782 | inputFormat, 783 | srsName 784 | } = unpack(features[0] || {}, params || {}, 'filter', 'inputFormat', 'srsName'); 785 | let replacements = translateFeatures([features[0]].filter(f => f), params || { 786 | srsName 787 | }); 788 | filter$$1 = filter(filter$$1, features, params); 789 | return wfs('Replace', { 790 | inputFormat, 791 | srsName 792 | }, replacements + filter$$1); 793 | } 794 | /** 795 | * Wraps the input actions in a wfs:Transaction. 796 | * @param {Object|string[]|string} actions an object mapping {Insert, Update, 797 | * Delete} to feature(s) to pass to Insert, Update, Delete, or wfs:action 798 | * string(s) to wrap in a transaction. 799 | * @param {TransactionParams} params optional srsName, lockId, releaseAction, 800 | * handle, inputFormat, version, and required nsAssignments, schemaLocations. 801 | * @return {string} A wfs:transaction wrapping the input actions. 802 | * @throws {Error} if `actions` is not an array of strings, a string, or 803 | * {@see Insert, @see Update, @see Delete}, where each action are valid inputs 804 | * to the eponymous function. 805 | */ 806 | 807 | function Transaction(actions, params = {}) { 808 | const transactionParams = ['srsName', 'lockId', 'releaseAction', 'handle']; 809 | let { 810 | version, 811 | // optional 812 | nsAssignments = {} // required 813 | 814 | } = params; // let converter = {Insert, Update, Delete}; 815 | 816 | let { 817 | insert: toInsert, 818 | update: toUpdate, 819 | delete: toDelete 820 | } = actions || {}; 821 | let finalActions = ''; // processedActions would be more accurate 822 | 823 | if (Array.isArray(actions) && actions.every(v => typeof v == 'string')) { 824 | finalActions += actions.join(''); 825 | } else if (typeof actions == 'string') { 826 | finalActions = actions; 827 | } else if ([toInsert, toUpdate, toDelete].some(e => e)) { 828 | finalActions += Insert(toInsert, params) + Update(toUpdate, params) + Delete(toDelete, params); 829 | } else { 830 | throw new Error(`unexpected input: ${JSON.stringify(actions)}`); 831 | } // generate schemaLocation, xmlns's 832 | 833 | 834 | let attrs = generateNsAssignments(nsAssignments, actions); 835 | attrs['xsi:schemaLocation'] = generateSchemaLines(params.schemaLocations); 836 | attrs['service'] = 'WFS'; 837 | attrs['version'] = /2\.0\.\d+/.exec(version || '') ? version : '2.0.0'; 838 | transactionParams.forEach(param => { 839 | if (params[param]) { 840 | attrs[param] = params[param]; 841 | } 842 | }); 843 | return wfs('Transaction', attrs, finalActions); 844 | } 845 | 846 | exports.Insert = Insert; 847 | exports.Update = Update; 848 | exports.Delete = Delete; 849 | exports.Replace = Replace; 850 | exports.Transaction = Transaction; 851 | 852 | Object.defineProperty(exports, '__esModule', { value: true }); 853 | 854 | })); 855 | -------------------------------------------------------------------------------- /dist/es5.es.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | /** 4 | * reorder coordinates to lat, lng iff config.coordinateOrder is false. 5 | * @param {Number[]} coords An array of coordinates, [lng, lat, ...etc] 6 | * @return {Number[]} An array of coordinates in the correct order. 7 | */ 8 | function orderCoords(coords){ 9 | { 10 | return coords; 11 | } 12 | if (coords[2]){ 13 | return [coords[1], coords[0], coords[2]]; 14 | } 15 | return coords.reverse(); 16 | } 17 | 18 | /** @private*/ 19 | function attrs(attrMappings){ 20 | // return Object.entries() 21 | let results = ''; 22 | for (let attrName in attrMappings){ 23 | let value = attrMappings[attrName]; 24 | results += (value ? ` ${attrName}="${value}"` : ''); 25 | } 26 | return results; 27 | } 28 | 29 | /** 30 | * Optional parameters for conversion of geojson to gml geometries 31 | * @typedef {Object} Params 32 | * @property {?String} params.srsName as string specifying SRS, e.g. 'EPSG:4326'. Only applies to multigeometries. 33 | * @property {?Number[]|?String[]} params.gmlIds an array of number/string gml:ids of the member geometries. 34 | * @property {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 35 | */ 36 | 37 | /** 38 | * A handler to compile geometries to multigeometries 39 | * @function 40 | * @param {String} name the name of the target multigeometry 41 | * @param {String} memberName the gml:tag of each multigeometry member. 42 | * @param {Object[]|Array} geom an array of geojson geometries 43 | * @param {String|Number} gmlId the gml:id of the multigeometry 44 | * @param {Params} params optional parameters. Omit gmlIds at your own risk, however. 45 | * @returns {String} a string containing gml describing the input multigeometry 46 | * @throws {Error} if a member geometry cannot be converted to gml 47 | */ 48 | function multi(name, memberName, membercb, geom, gmlId, params={}){ 49 | var {srsName, gmlIds} = params; 50 | let multi = ``; 51 | multi += ``; 52 | geom.forEach(function(member, i){ 53 | let _gmlId = member.id || (gmlIds || [])[i] || ''; 54 | if (name == 'MultiGeometry'){ 55 | let memberType = member.type; 56 | member = member.coordinates; 57 | multi += membercb[memberType](member, _gmlId, params); 58 | } else { 59 | multi += membercb(member, _gmlId, params); 60 | } 61 | }); 62 | multi += ``; 63 | return multi + ``; 64 | } 65 | /** 66 | * Converts an input geojson Point geometry to gml 67 | * @function 68 | * @param {Number[]} coords the coordinates member of the geojson geometry 69 | * @param {String|Number} gmlId the gml:id 70 | * @param {Params} params optional parameters 71 | * @returns {String} a string containing gml representing the input geometry 72 | */ 73 | function point(coords, gmlId, params={}){ 74 | var {srsName:srsName, srsDimension:srsDimension} = params; 75 | return `` + 76 | `` + 77 | orderCoords(coords).join(' ') + 78 | '' + 79 | ''; 80 | } 81 | /** 82 | * Converts an input geojson LineString geometry to gml 83 | * @function 84 | * @param {Number[][]} coords the coordinates member of the geojson geometry 85 | * @param {String|Number} gmlId the gml:id 86 | * @param {Params} params optional parameters 87 | * @returns {String} a string containing gml representing the input geometry 88 | */ 89 | function lineString(coords, gmlId, params={}){ 90 | var {srsName:srsName, srsDimension:srsDimension} = params; 91 | return `` + 92 | `` + 93 | coords.map((e)=>orderCoords(e).join(' ')).join(' ') + 94 | '' + 95 | ''; 96 | } 97 | /** 98 | * Converts an input geojson LinearRing member of a polygon geometry to gml 99 | * @function 100 | * @param {Number[][]} coords the coordinates member of the geojson geometry 101 | * @param {String|Number} gmlId the gml:id 102 | * @param {Params} params optional parameters 103 | * @returns {String} a string containing gml representing the input geometry 104 | */ 105 | function linearRing(coords, gmlId, params={}){ 106 | var {srsName:srsName, srsDimension:srsDimension} = params; 107 | return `` + 108 | `` + 109 | coords.map((e)=>orderCoords(e).join(' ')).join(' ') + 110 | '' + 111 | ''; 112 | } 113 | /** 114 | * Converts an input geojson Polygon geometry to gml 115 | * @function 116 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 117 | * @param {String|Number} gmlId the gml:id 118 | * @param {Params} params optional parameters 119 | * @returns {String} a string containing gml representing the input geometry 120 | */ 121 | function polygon(coords, gmlId, params={}){ 122 | // geom.coordinates are arrays of LinearRings 123 | var {srsName} = params; 124 | let polygon = `` + 125 | '' + 126 | linearRing(coords[0]) + 127 | ''; 128 | if (coords.length >= 2){ 129 | for (let ring of coords.slice(1)){ 130 | polygon += '' + 131 | linearRing(ring) + 132 | ''; 133 | } 134 | } 135 | polygon += ''; 136 | return polygon; 137 | } 138 | /** 139 | * Converts an input geojson MultiPoint geometry to gml 140 | * @function 141 | * @param {Number[][]} coords the coordinates member of the geojson geometry 142 | * @param {String|Number} gmlId the gml:id 143 | * @param {Params} params optional parameters 144 | * @returns {String} a string containing gml representing the input geometry 145 | */ 146 | function multiPoint(coords, gmlId, params={}){ 147 | return multi('MultiPoint', 'pointMembers', point, coords, gmlId, params); 148 | } 149 | 150 | /** 151 | * Converts an input geojson MultiLineString geometry to gml 152 | * @function 153 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 154 | * @param {String|Number} gmlId the gml:id 155 | * @param {Params} params optional parameters 156 | * @returns {String} a string containing gml representing the input geometry 157 | */ 158 | function multiLineString(coords, gmlId, params={}){ 159 | return multi('MultiCurve', 'curveMembers', lineString, coords, gmlId, params); 160 | } 161 | /** 162 | * Converts an input geojson MultiPolygon geometry to gml 163 | * @function 164 | * @param {Number[][][][]} coords the coordinates member of the geojson geometry 165 | * @param {String|Number} gmlId the gml:id 166 | * @param {Params} params optional parameters 167 | * @returns {String} a string containing gml representing the input geometry 168 | */ 169 | function multiPolygon(coords, gmlId, params={}){ 170 | return multi('MultiSurface', 'surfaceMembers', polygon, coords, gmlId, params); 171 | } 172 | 173 | /** 174 | * A helper to de-camelcase this module's geometry conversion methods 175 | * @param {Object} obj a mapping of camelcase geometry types to converter functions 176 | * @return {Object} a mapping of capitalized geometry types to converter functions 177 | * @example 178 | * makeConverter({lineString}) 179 | * // returns {LineString: lineString} 180 | */ 181 | function makeConverter(obj) { 182 | return Object.entries(obj).map(([type, converter]) => { 183 | return {[type[0].toUpperCase() + type.slice(1)]: converter}; 184 | }).reduce((a, b) => Object.assign(a, b), {}); 185 | 186 | } 187 | /** 188 | * A helper to map geometry types to converter functions. 189 | * @function 190 | * @param {Object} obj an object mapping camelcase-d geometry type names to 191 | * converter functions for that type. 192 | * @example 193 | * import {point, lineString} from 'geojson-to-gml-3'; 194 | * const geomToGml = makeTranslator({point, lineString}); 195 | * geomToGml({type: 'Point', coordinates: [0, 0]}); 196 | */ 197 | function makeTranslator(obj) { 198 | const converter = makeConverter(obj); 199 | return function (geom, gmlId, params){ 200 | const warn = () => new Error(`unkown: ${geom.type} ` + [...arguments].join()); 201 | const convert = converter[geom.type] || warn; 202 | return convert( 203 | geom.coordinates || geom.geometries, 204 | gmlId, 205 | params 206 | ); 207 | }; 208 | } 209 | 210 | /** 211 | * a namespace to switch between geojson-handling functions by geojson.type 212 | * @const 213 | * @type {Object} 214 | */ 215 | const allTypes = makeConverter({ 216 | point, lineString, linearRing, polygon, multiPoint, multiLineString, 217 | multiPolygon 218 | }); 219 | /** 220 | * Converts an input geojson GeometryCollection geometry to gml 221 | * @function 222 | * @param {Object[]} coords the coordinates member of the geojson geometry 223 | * @param {String|Number} gmlId the gml:id 224 | * @param {Object} params optional parameters 225 | * @param {?String} params.srsName as string specifying SRS 226 | * @param {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 227 | * @returns {String} a string containing gml representing the input geometry 228 | */ 229 | function geometryCollection(geoms, gmlId, params={}) { 230 | return multi( 231 | 'MultiGeometry', 'geometryMembers', allTypes, geoms, gmlId, 232 | params 233 | ); 234 | } 235 | 236 | /** 237 | * Translates any geojson geometry into GML 3.2.1 238 | * @public 239 | * @function 240 | * @param {Object} geom a geojson geometry object 241 | * @param {?Array} geom.coordinates the nested array of coordinates forming the geometry 242 | * @param {?Object[]} geom.geometries for a GeometryCollection only, the array of member geometry objects 243 | * @param {String|Number} gmlId the gml:id of the geometry 244 | * @param {object} params optional parameters 245 | * @param {?String} params.srsName a string specifying the SRS 246 | * @param {?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 247 | * @param {?Number[]|?String[]} gmlIds an array of number/string gml:ids of the member geometries of a multigeometry. 248 | * @returns {String} a valid gml string describing the input geojson geometry 249 | */ 250 | const geomToGml = makeTranslator( 251 | Object.assign({geometryCollection}, allTypes) 252 | ); 253 | 254 | require('../../modules/es7.object.entries'); 255 | module.exports = require('../../modules/_core').Object.entries; 256 | 257 | /** 258 | * xml utilities. 259 | * @module xml 260 | */ 261 | 262 | /** 263 | * Turns an object into a string of xml attribute key-value pairs. 264 | * @function 265 | * @param {Object} attrs an object mapping attribute names to attribute values 266 | * @return {String} a string of xml attribute key-value pairs 267 | */ 268 | function attrs$1(attrs) { 269 | return Object.keys(attrs).map(function (a) { 270 | return attrs[a] ? ' ' + a + '="' + escape(attrs[a]) + '"' : ''; 271 | }).join(''); 272 | } 273 | 274 | /** 275 | * Creates a string xml tag. 276 | * @function 277 | * @param {String} ns the tag's xml namespace abbreviation. 278 | * @param {String} tagName the tag name. 279 | * @param {Object} attrsObj @see xml.attrs. 280 | * @param {String} inner inner xml. 281 | * @return {String} an xml string. 282 | */ 283 | function tag(ns, tagName, attrsObj, inner) { 284 | var tag = (ns ? ns + ':' : '') + tagName; 285 | if (tagName) { 286 | return '<' + tag + attrs$1(attrsObj) + (inner !== null ? '>' + inner + ''; 287 | } else { 288 | throw new Error('no tag supplied ' + JSON.stringify({ ns: ns, tagName: tagName, attrsObj: attrsObj, inner: inner }, null, 2)); 289 | } 290 | } 291 | /** 292 | * Shorthand for creating a wfs xml tag. 293 | * @param {String} tagName a valid wfs tag name. 294 | * @param {Object} attrsObj @see xml.attrs. 295 | * @param {String} inner @see xml.tag. 296 | * @return {String} a wfs element. 297 | */ 298 | var wfs = function wfs(tagName, attrsObj, inner) { 299 | return tag('wfs', tagName, attrsObj, inner); 300 | }; 301 | 302 | /** 303 | * Creates a fes:ResourceId filter from a layername and id 304 | * @function 305 | * @param {String} lyr layer name of the filtered feature 306 | * @param {String} id feature id 307 | * @return {String} a filter-ecoding of the filter. 308 | */ 309 | var idFilter = function idFilter(lyr, id$$1) { 310 | return ''; 311 | }; 312 | 313 | /** 314 | * Creates an xml-safe string from a given input string 315 | * @function 316 | * @param {String} input String to escape 317 | * @return {String} XML-safe string 318 | */ 319 | function escape(input) { 320 | if (typeof input !== 'string') { 321 | // Backup check for non-strings 322 | return input; 323 | } 324 | 325 | var output = input.replace(/[<>&'"]/g, function (char) { 326 | switch (char) { 327 | case '<': 328 | return '<'; 329 | case '>': 330 | return '>'; 331 | case '&': 332 | return '&'; 333 | case '\'': 334 | return '''; 335 | case '"': 336 | return '"'; 337 | } 338 | }); 339 | 340 | return output; 341 | } 342 | 343 | /* eslint-disable camelcase */ 344 | /** 345 | * Common utilities for handling parameters for creation of WFS trasactions. 346 | * @module utils 347 | */ 348 | 349 | /** 350 | * Iterates over the key-value pairs, filtering by a whitelist if available. 351 | * @function 352 | * @param {Array} whitelist a whitelist of property names 353 | * @param {Object} properties an object mapping property names to values 354 | * @param {Function} cb a function to call on each (whitelisted key, value) pair 355 | */ 356 | var useWhitelistIfAvailable = function useWhitelistIfAvailable(whitelist, properties, cb) { 357 | var _iteratorNormalCompletion = true; 358 | var _didIteratorError = false; 359 | var _iteratorError = undefined; 360 | 361 | try { 362 | for (var _iterator = (whitelist || Object.keys(properties))[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 363 | var prop = _step.value; 364 | 365 | var val = properties[prop]; 366 | if (Number.isNaN(val)) { 367 | throw new Error('NaN is not allowed.'); 368 | } 369 | if (val !== undefined) { 370 | cb(prop, val); 371 | } 372 | } 373 | } catch (err) { 374 | _didIteratorError = true; 375 | _iteratorError = err; 376 | } finally { 377 | try { 378 | if (!_iteratorNormalCompletion && _iterator.return) { 379 | _iterator.return(); 380 | } 381 | } finally { 382 | if (_didIteratorError) { 383 | throw _iteratorError; 384 | } 385 | } 386 | } 387 | }; 388 | 389 | var featureMembers = new Set(['properties', 'geometry', 'id', 'layer']); 390 | /** 391 | * Resolves attributes from feature, then params unless they are normally 392 | * found in the feature 393 | * @param {Object} feature a geojson feature 394 | * @param {Object} params an object of backup / override parameters 395 | * @param {Array} args parameter names to resolve from feature or 396 | * params 397 | * @return {Object} an object mapping each named parameter to its resolved 398 | * value 399 | */ 400 | function unpack(feature, params) { 401 | var results = {}; 402 | 403 | for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { 404 | args[_key - 2] = arguments[_key]; 405 | } 406 | 407 | var _iteratorNormalCompletion2 = true; 408 | var _didIteratorError2 = false; 409 | var _iteratorError2 = undefined; 410 | 411 | try { 412 | for (var _iterator2 = args[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 413 | var arg = _step2.value; 414 | 415 | if (arg === 'layer') { 416 | results[arg] = (params.layer || {}).id || params.layer || (feature.layer || {}).id || feature.layer || ''; 417 | } else if (!featureMembers.has(arg)) { 418 | results[arg] = feature[arg] || params[arg] || ''; 419 | } else { 420 | results[arg] = params[arg] || feature[arg] || ''; 421 | } 422 | } 423 | } catch (err) { 424 | _didIteratorError2 = true; 425 | _iteratorError2 = err; 426 | } finally { 427 | try { 428 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 429 | _iterator2.return(); 430 | } 431 | } finally { 432 | if (_didIteratorError2) { 433 | throw _iteratorError2; 434 | } 435 | } 436 | } 437 | 438 | return results; 439 | } 440 | /** 441 | * Generates an object to be passed to @see xml.attrs xmlns:ns="uri" definitions 442 | * for a wfs:Transaction 443 | * @param {Object} nsAssignments @see Params.nsAssignments 444 | * @param {String} xml arbitrary xml. 445 | * @return {Object} an object mapping each ns to its URI as 'xmlns:ns' : 'URI'. 446 | * @throws {Error} if any namespace used within `xml` is missing a URI 447 | * definition 448 | */ 449 | function generateNsAssignments(nsAssignments, xml) { 450 | var attrs = {}; 451 | var makeNsAssignment = function makeNsAssignment(ns, uri) { 452 | return attrs['xmlns:' + ns] = uri; 453 | }; 454 | Object.keys(nsAssignments).forEach(function (ns) { 455 | makeNsAssignment(ns, nsAssignments[ns]); 456 | }); 457 | // check all ns's assigned 458 | var re = /(<|typeName=")(\w+):/g; 459 | var arr = void 0; 460 | var allNamespaces = new Set(); 461 | while ((arr = re.exec(xml)) !== null) { 462 | allNamespaces.add(arr[2]); 463 | } 464 | if (allNamespaces.has('fes')) { 465 | makeNsAssignment('fes', 'http://www.opengis.net/fes/2.0'); 466 | } makeNsAssignment('xsi', 'http://www.w3.org/2001/XMLSchema-instance'); 467 | makeNsAssignment('gml', 'http://www.opengis.net/gml/3.2'); 468 | makeNsAssignment('wfs', 'http://www.opengis.net/wfs/2.0'); 469 | 470 | var _iteratorNormalCompletion3 = true; 471 | var _didIteratorError3 = false; 472 | var _iteratorError3 = undefined; 473 | 474 | try { 475 | for (var _iterator3 = allNamespaces[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 476 | var ns = _step3.value; 477 | 478 | if (!attrs['xmlns:' + ns]) { 479 | throw new Error('unassigned namespace ' + ns); 480 | } 481 | } /* , schemaLocations*/ 482 | } catch (err) { 483 | _didIteratorError3 = true; 484 | _iteratorError3 = err; 485 | } finally { 486 | try { 487 | if (!_iteratorNormalCompletion3 && _iterator3.return) { 488 | _iterator3.return(); 489 | } 490 | } finally { 491 | if (_didIteratorError3) { 492 | throw _iteratorError3; 493 | } 494 | } 495 | } 496 | 497 | return attrs; 498 | } 499 | 500 | /** 501 | * Returns a string alternating uri, whitespace, and the uri's schema's 502 | * location. 503 | * @param {Object} schemaLocations an object mapping uri:schemalocation 504 | * @return {string} a string that is a valid xsi:schemaLocation value. 505 | */ 506 | function generateSchemaLines() { 507 | var schemaLocations = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 508 | 509 | // TODO: add ns assignment check 510 | schemaLocations['http://www.opengis.net/wfs/2.0'] = 'http://schemas.opengis.net/wfs/2.0/wfs.xsd'; 511 | var schemaLines = []; 512 | Object.entries(schemaLocations).forEach(function (entry) { 513 | return schemaLines.push(entry.join('\n')); 514 | }); 515 | return schemaLines.join('\n'); 516 | } 517 | 518 | /** 519 | * Turns an array of geojson features into gml:_feature strings describing them. 520 | * @function 521 | * @param {Feature[]} features an array of features to translate to 522 | * gml:_features. 523 | * @param {Params} params an object of backup / override parameters 524 | * @return {String} a gml:_feature string. 525 | */ 526 | function translateFeatures(features) { 527 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 528 | 529 | var inner = ''; 530 | var srsName = params.srsName, 531 | srsDimension = params.srsDimension; 532 | 533 | var _loop = function _loop(feature) { 534 | // TODO: add whitelist support 535 | var _unpack = unpack(feature, params, 'ns', 'layer', 'geometry_name', 'properties', 'id', 'whitelist'), 536 | ns = _unpack.ns, 537 | layer = _unpack.layer, 538 | geometry_name = _unpack.geometry_name, 539 | properties = _unpack.properties, 540 | id$$1 = _unpack.id, 541 | whitelist = _unpack.whitelist; 542 | 543 | var fields = ''; 544 | if (geometry_name) { 545 | fields += tag(ns, geometry_name, {}, geomToGml(feature.geometry, '', { srsName: srsName, srsDimension: srsDimension })); 546 | } 547 | useWhitelistIfAvailable(whitelist, properties, function (prop, val) { 548 | if (val === null) { 549 | return fields; 550 | } 551 | return fields += tag(ns, prop, {}, escape(properties[prop])); 552 | }); 553 | inner += tag(ns, layer, { 'gml:id': id(layer, id$$1) }, fields); 554 | }; 555 | 556 | var _iteratorNormalCompletion4 = true; 557 | var _didIteratorError4 = false; 558 | var _iteratorError4 = undefined; 559 | 560 | try { 561 | for (var _iterator4 = features[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { 562 | var feature = _step4.value; 563 | 564 | _loop(feature); 565 | } 566 | } catch (err) { 567 | _didIteratorError4 = true; 568 | _iteratorError4 = err; 569 | } finally { 570 | try { 571 | if (!_iteratorNormalCompletion4 && _iterator4.return) { 572 | _iterator4.return(); 573 | } 574 | } finally { 575 | if (_didIteratorError4) { 576 | throw _iteratorError4; 577 | } 578 | } 579 | } 580 | 581 | return inner; 582 | } 583 | 584 | /** 585 | * common checks, coersions, and informative errors/ warnings 586 | * @module ensure 587 | */ 588 | 589 | /** 590 | * Ensures the result is an array. 591 | * @private 592 | * @function 593 | * @param {Feature|Feature[]|FeatureCollection} maybe a GeoJSON 594 | * FeatureCollection, Feature, or an array of Features. 595 | * @return {Feature[]} 596 | */ 597 | var array = function array() { 598 | var _ref; 599 | 600 | for (var _len = arguments.length, maybe = Array(_len), _key = 0; _key < _len; _key++) { 601 | maybe[_key] = arguments[_key]; 602 | } 603 | 604 | return (maybe[0].features || (_ref = []).concat.apply(_ref, maybe)).filter(function (f) { 605 | return f; 606 | }); 607 | }; 608 | /** 609 | * Ensures a layer.id format of an input id. 610 | * @function 611 | * @param {String} lyr layer name 612 | * @param {String} id id, possibly already in correct layer.id format. 613 | * @return {String} a correctly-formatted gml:id 614 | */ 615 | var id = function id(lyr, _id) { 616 | return (/\./.exec(_id || '') ? _id : lyr + '.' + _id 617 | ); 618 | }; 619 | /** 620 | * return a correctly-formatted typeName 621 | * @function 622 | * @param {String} ns namespace 623 | * @param {String} layer layer name 624 | * @param {String} typeName typeName to check 625 | * @return {String} a correctly-formatted typeName 626 | * @throws {Error} if typeName it cannot form a typeName from ns and layer 627 | */ 628 | var typeName = function typeName(ns, layer, _typeName) { 629 | if (!_typeName && !(ns && layer)) { 630 | throw new Error('no typename possible: ' + JSON.stringify({ typeName: _typeName, ns: ns, layer: layer }, null, 2)); 631 | } 632 | return _typeName || ns + ':' + layer + 'Type'; 633 | }; 634 | 635 | /** 636 | * Builds a filter from feature ids if one is not already input. 637 | * @function 638 | * @param {?String} filter a possible string filter 639 | * @param {Array} features an array of geojson feature objects 640 | * @param {Object} params an object of backup / override parameters 641 | * @return {String} A filter, or the input filter if it was a string. 642 | */ 643 | function filter(filter, features, params) { 644 | if (!filter) { 645 | filter = ''; 646 | var _iteratorNormalCompletion = true; 647 | var _didIteratorError = false; 648 | var _iteratorError = undefined; 649 | 650 | try { 651 | for (var _iterator = features[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 652 | var feature = _step.value; 653 | 654 | var layer = unpack(feature, params); 655 | filter += idFilter(layer, feature.id); 656 | } 657 | } catch (err) { 658 | _didIteratorError = true; 659 | _iteratorError = err; 660 | } finally { 661 | try { 662 | if (!_iteratorNormalCompletion && _iterator.return) { 663 | _iterator.return(); 664 | } 665 | } finally { 666 | if (_didIteratorError) { 667 | throw _iteratorError; 668 | } 669 | } 670 | } 671 | 672 | return '' + filter + ''; 673 | } else { 674 | return filter; 675 | } 676 | } 677 | 678 | /* eslint-disable camelcase, new-cap */ 679 | var wfs$1 = wfs; 680 | 681 | /** 682 | * Returns a wfs:Insert tag wrapping a translated feature 683 | * @function 684 | * @param {Feature[]|FeatureCollection|Feature} features Feature(s) to pass to 685 | * @see translateFeatures 686 | * @param {Params} params to be passed to @see translateFeatures, with optional 687 | * inputFormat, srsName, handle for the wfs:Insert tag. 688 | * @return {string} a wfs:Insert string. 689 | */ 690 | 691 | function Insert(features) { 692 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 693 | 694 | features = array(features); 695 | var inputFormat = params.inputFormat, 696 | srsName = params.srsName, 697 | handle = params.handle; 698 | 699 | if (!features.length) { 700 | console.warn('no features supplied'); 701 | return ''; 702 | } 703 | var toInsert = translateFeatures(features, params); 704 | return tag('wfs', 'Insert', { inputFormat: inputFormat, srsName: srsName, handle: handle }, toInsert); 705 | } 706 | 707 | /** 708 | * Updates the input features in bulk with params.properties or by id. 709 | * @param {Feature[]|FeatureCollection} features features to update. These may 710 | * pass in geometry_name, properties, and layer (overruled by params) and 711 | * ns, layer, srsName (overruling params). 712 | * @param {Params} params with optional properties, ns (namespace), layer, 713 | * geometry_name, filter, typeName, whitelist. 714 | * @return {string} a string wfs:Upate action. 715 | */ 716 | function Update(features) { 717 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 718 | 719 | features = array(features); 720 | /** 721 | * makes a wfs:Property string containg a wfs:ValueReference, wfs:Value pair. 722 | * @private 723 | * @function 724 | * @memberof Update~ 725 | * @param {string} prop the field/property name 726 | * @param {string} val the field/property value 727 | * @param {string} action one of 'insertBefore', 'insertAfter', 'remove', 728 | * 'replace'. See [OGC 09-025r2 § 15.2.5.2.1]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286}. 729 | * `action` would delete or modify the order of fields within the remote 730 | * feature. There is currently no way to input `action,` since wfs:Update's 731 | * default action, 'replace', is sufficient. 732 | * @return {string} a wfs:Property(wfs:ValueReference) pair. 733 | */ 734 | var makeKvp = function makeKvp(prop, val, action$$1) { 735 | var value = ''; 736 | if (val === null) { 737 | value = wfs$1('Value', { 'xsi:nil': true }, ''); 738 | } else if (val !== undefined) { 739 | value = wfs$1('Value', {}, val); 740 | } 741 | 742 | return wfs$1('Property', {}, wfs$1('ValueReference', { action: action$$1 }, prop) + value); 743 | }; 744 | 745 | if (params.properties) { 746 | var inputFormat = params.inputFormat, 747 | filter$$1 = params.filter, 748 | typeName$$1 = params.typeName, 749 | whitelist = params.whitelist; 750 | 751 | var _unpack = unpack(features[0] || {}, params, 'srsName', 'ns', 'layer', 'geometry_name'), 752 | srsName = _unpack.srsName, 753 | ns = _unpack.ns, 754 | layer = _unpack.layer, 755 | geometry_name = _unpack.geometry_name; 756 | 757 | typeName$$1 = typeName(ns, layer, typeName$$1); 758 | filter$$1 = filter(filter$$1, features, params); 759 | if (!filter$$1 && !features.length) { 760 | console.warn('neither features nor filter supplied'); 761 | return ''; 762 | } 763 | var fields = ''; 764 | useWhitelistIfAvailable( // TODO: action attr 765 | whitelist, params.properties, function (k, v) { 766 | return fields += makeKvp(k, escape(v)); 767 | }); 768 | if (geometry_name) { 769 | fields += makeKvp(geometry_name, tag(ns, geometry_name, {}, geomToGml(params.geometry, '', { srsName: srsName }))); 770 | } 771 | return wfs$1('Update', { inputFormat: inputFormat, srsName: srsName, typeName: typeName$$1 }, fields + filter$$1); 772 | } else { 773 | // encapsulate each update in its own Update tag 774 | return features.map(function (f) { 775 | return Update(f, Object.assign({}, params, { properties: f.properties })); 776 | }).join(''); 777 | } 778 | } 779 | 780 | /** 781 | * Creates a wfs:Delete action, creating a filter and typeName from feature ids 782 | * if none are supplied. 783 | * @param {Feature[]|FeatureCollection|Feature} features 784 | * @param {Params} params optional parameter overrides. 785 | * @param {string} [params.ns] @see Params.ns 786 | * @param {string|Object} [params.layer] @see Params.layer 787 | * @param {string} [params.typeName] @see Params.typeName. This will be inferred 788 | * from feature/params layer and ns if this is left undefined. 789 | * @param {filter} [params.filter] @see Params.filter. This will be inferred 790 | * from feature ids and layer(s) if left undefined (@see ensure.filter). 791 | * @return {string} a wfs:Delete string. 792 | */ 793 | function Delete(features) { 794 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 795 | 796 | features = array(features); 797 | var filter$$1 = params.filter, 798 | typeName$$1 = params.typeName; // TODO: recur & encapsulate by typeName 799 | 800 | var _unpack2 = unpack(features[0] || {}, params, 'layer', 'ns'), 801 | ns = _unpack2.ns, 802 | layer = _unpack2.layer; 803 | 804 | typeName$$1 = typeName(ns, layer, typeName$$1); 805 | filter$$1 = filter(filter$$1, features, params); 806 | return wfs$1('Delete', { typeName: typeName$$1 }, filter$$1); 807 | } 808 | 809 | /** 810 | * Returns a string wfs:Replace action. 811 | * @param {Feature[]|FeatureCollection|Feature} features feature(s) to replace 812 | * @param {Params} params with optional filter, inputFormat, srsName 813 | * @return {string} a string wfs:Replace action. 814 | */ 815 | function Replace(features) { 816 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 817 | 818 | features = array(features); 819 | 820 | var _unpack3 = unpack(features[0] || {}, params || {}, 'filter', 'inputFormat', 'srsName'), 821 | filter$$1 = _unpack3.filter, 822 | inputFormat = _unpack3.inputFormat, 823 | srsName = _unpack3.srsName; 824 | 825 | var replacements = translateFeatures([features[0]].filter(function (f) { 826 | return f; 827 | }), params || { srsName: srsName }); 828 | filter$$1 = filter(filter$$1, features, params); 829 | return wfs$1('Replace', { inputFormat: inputFormat, srsName: srsName }, replacements + filter$$1); 830 | } 831 | 832 | /** 833 | * Wraps the input actions in a wfs:Transaction. 834 | * @param {Object|string[]|string} actions an object mapping {Insert, Update, 835 | * Delete} to feature(s) to pass to Insert, Update, Delete, or wfs:action 836 | * string(s) to wrap in a transaction. 837 | * @param {TransactionParams} params optional srsName, lockId, releaseAction, 838 | * handle, inputFormat, version, and required nsAssignments, schemaLocations. 839 | * @return {string} A wfs:transaction wrapping the input actions. 840 | * @throws {Error} if `actions` is not an array of strings, a string, or 841 | * {@see Insert, @see Update, @see Delete}, where each action are valid inputs 842 | * to the eponymous function. 843 | */ 844 | function Transaction(actions) { 845 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 846 | 847 | var transactionParams = ['srsName', 'lockId', 'releaseAction', 'handle']; 848 | var version = params.version, 849 | _params$nsAssignments = params.nsAssignments, 850 | nsAssignments = _params$nsAssignments === undefined ? {} : _params$nsAssignments; 851 | // let converter = {Insert, Update, Delete}; 852 | 853 | var _ref = actions || {}, 854 | toInsert = _ref.insert, 855 | toUpdate = _ref.update, 856 | toDelete = _ref.delete; 857 | 858 | var finalActions = ''; // processedActions would be more accurate 859 | 860 | if (Array.isArray(actions) && actions.every(function (v) { 861 | return typeof v == 'string'; 862 | })) { 863 | finalActions += actions.join(''); 864 | } else if (typeof actions == 'string') { 865 | finalActions = actions; 866 | } else if ([toInsert, toUpdate, toDelete].some(function (e) { 867 | return e; 868 | })) { 869 | finalActions += Insert(toInsert, params) + Update(toUpdate, params) + Delete(toDelete, params); 870 | } else { 871 | throw new Error('unexpected input: ' + JSON.stringify(actions)); 872 | } 873 | // generate schemaLocation, xmlns's 874 | var attrs = generateNsAssignments(nsAssignments, actions); 875 | attrs['xsi:schemaLocation'] = generateSchemaLines(params.schemaLocations); 876 | attrs['service'] = 'WFS'; 877 | attrs['version'] = /2\.0\.\d+/.exec(version || '') ? version : '2.0.0'; 878 | transactionParams.forEach(function (param) { 879 | if (params[param]) { 880 | attrs[param] = params[param]; 881 | } 882 | }); 883 | return wfs$1('Transaction', attrs, finalActions); 884 | } 885 | 886 | export { Insert, Update, Delete, Replace, Transaction }; 887 | -------------------------------------------------------------------------------- /dist/es5.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | function _defineProperty(obj, key, value) { 6 | if (key in obj) { 7 | Object.defineProperty(obj, key, { 8 | value: value, 9 | enumerable: true, 10 | configurable: true, 11 | writable: true 12 | }); 13 | } else { 14 | obj[key] = value; 15 | } 16 | 17 | return obj; 18 | } 19 | 20 | function _slicedToArray(arr, i) { 21 | return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); 22 | } 23 | 24 | function _toConsumableArray(arr) { 25 | return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); 26 | } 27 | 28 | function _arrayWithoutHoles(arr) { 29 | if (Array.isArray(arr)) { 30 | for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; 31 | 32 | return arr2; 33 | } 34 | } 35 | 36 | function _arrayWithHoles(arr) { 37 | if (Array.isArray(arr)) return arr; 38 | } 39 | 40 | function _iterableToArray(iter) { 41 | if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); 42 | } 43 | 44 | function _iterableToArrayLimit(arr, i) { 45 | var _arr = []; 46 | var _n = true; 47 | var _d = false; 48 | var _e = undefined; 49 | 50 | try { 51 | for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 52 | _arr.push(_s.value); 53 | 54 | if (i && _arr.length === i) break; 55 | } 56 | } catch (err) { 57 | _d = true; 58 | _e = err; 59 | } finally { 60 | try { 61 | if (!_n && _i["return"] != null) _i["return"](); 62 | } finally { 63 | if (_d) throw _e; 64 | } 65 | } 66 | 67 | return _arr; 68 | } 69 | 70 | function _nonIterableSpread() { 71 | throw new TypeError("Invalid attempt to spread non-iterable instance"); 72 | } 73 | 74 | function _nonIterableRest() { 75 | throw new TypeError("Invalid attempt to destructure non-iterable instance"); 76 | } 77 | 78 | /** 79 | * reorder coordinates to lat, lng iff config.coordinateOrder is false. 80 | * @param {Number[]} coords An array of coordinates, [lng, lat, ...etc] 81 | * @return {Number[]} An array of coordinates in the correct order. 82 | */ 83 | 84 | function orderCoords(coords) { 85 | { 86 | return coords; 87 | } 88 | 89 | if (coords[2]) { 90 | return [coords[1], coords[0], coords[2]]; 91 | } 92 | 93 | return coords.reverse(); 94 | } 95 | /** @private*/ 96 | 97 | 98 | function attrs(attrMappings) { 99 | // return Object.entries() 100 | var results = ''; 101 | 102 | for (var attrName in attrMappings) { 103 | var value = attrMappings[attrName]; 104 | results += value ? " ".concat(attrName, "=\"").concat(value, "\"") : ''; 105 | } 106 | 107 | return results; 108 | } 109 | /** 110 | * Optional parameters for conversion of geojson to gml geometries 111 | * @typedef {Object} Params 112 | * @property {?String} params.srsName as string specifying SRS, e.g. 'EPSG:4326'. Only applies to multigeometries. 113 | * @property {?Number[]|?String[]} params.gmlIds an array of number/string gml:ids of the member geometries. 114 | * @property {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 115 | */ 116 | 117 | /** 118 | * A handler to compile geometries to multigeometries 119 | * @function 120 | * @param {String} name the name of the target multigeometry 121 | * @param {String} memberName the gml:tag of each multigeometry member. 122 | * @param {Object[]|Array} geom an array of geojson geometries 123 | * @param {String|Number} gmlId the gml:id of the multigeometry 124 | * @param {Params} params optional parameters. Omit gmlIds at your own risk, however. 125 | * @returns {String} a string containing gml describing the input multigeometry 126 | * @throws {Error} if a member geometry cannot be converted to gml 127 | */ 128 | 129 | 130 | function multi(name, memberName, membercb, geom, gmlId) { 131 | var params = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {}; 132 | var srsName = params.srsName, 133 | gmlIds = params.gmlIds; 134 | var multi = ""); 138 | multi += ""); 139 | geom.forEach(function (member, i) { 140 | var _gmlId = member.id || (gmlIds || [])[i] || ''; 141 | 142 | if (name == 'MultiGeometry') { 143 | var memberType = member.type; 144 | member = member.coordinates; 145 | multi += membercb[memberType](member, _gmlId, params); 146 | } else { 147 | multi += membercb(member, _gmlId, params); 148 | } 149 | }); 150 | multi += ""); 151 | return multi + ""); 152 | } 153 | /** 154 | * Converts an input geojson Point geometry to gml 155 | * @function 156 | * @param {Number[]} coords the coordinates member of the geojson geometry 157 | * @param {String|Number} gmlId the gml:id 158 | * @param {Params} params optional parameters 159 | * @returns {String} a string containing gml representing the input geometry 160 | */ 161 | 162 | 163 | function point(coords, gmlId) { 164 | var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 165 | var srsName = params.srsName, 166 | srsDimension = params.srsDimension; 167 | return "") + "") + orderCoords(coords).join(' ') + '' + ''; 173 | } 174 | /** 175 | * Converts an input geojson LineString geometry to gml 176 | * @function 177 | * @param {Number[][]} coords the coordinates member of the geojson geometry 178 | * @param {String|Number} gmlId the gml:id 179 | * @param {Params} params optional parameters 180 | * @returns {String} a string containing gml representing the input geometry 181 | */ 182 | 183 | function lineString(coords, gmlId) { 184 | var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 185 | var srsName = params.srsName, 186 | srsDimension = params.srsDimension; 187 | return "") + "") + coords.map(function (e) { 193 | return orderCoords(e).join(' '); 194 | }).join(' ') + '' + ''; 195 | } 196 | /** 197 | * Converts an input geojson LinearRing member of a polygon geometry to gml 198 | * @function 199 | * @param {Number[][]} coords the coordinates member of the geojson geometry 200 | * @param {String|Number} gmlId the gml:id 201 | * @param {Params} params optional parameters 202 | * @returns {String} a string containing gml representing the input geometry 203 | */ 204 | 205 | function linearRing(coords, gmlId) { 206 | var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 207 | var srsName = params.srsName, 208 | srsDimension = params.srsDimension; 209 | return "") + "") + coords.map(function (e) { 215 | return orderCoords(e).join(' '); 216 | }).join(' ') + '' + ''; 217 | } 218 | /** 219 | * Converts an input geojson Polygon geometry to gml 220 | * @function 221 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 222 | * @param {String|Number} gmlId the gml:id 223 | * @param {Params} params optional parameters 224 | * @returns {String} a string containing gml representing the input geometry 225 | */ 226 | 227 | function polygon(coords, gmlId) { 228 | var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 229 | // geom.coordinates are arrays of LinearRings 230 | var srsName = params.srsName; 231 | var polygon = "") + '' + linearRing(coords[0]) + ''; 235 | 236 | if (coords.length >= 2) { 237 | var _iteratorNormalCompletion = true; 238 | var _didIteratorError = false; 239 | var _iteratorError = undefined; 240 | 241 | try { 242 | for (var _iterator = coords.slice(1)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 243 | var ring = _step.value; 244 | polygon += '' + linearRing(ring) + ''; 245 | } 246 | } catch (err) { 247 | _didIteratorError = true; 248 | _iteratorError = err; 249 | } finally { 250 | try { 251 | if (!_iteratorNormalCompletion && _iterator.return != null) { 252 | _iterator.return(); 253 | } 254 | } finally { 255 | if (_didIteratorError) { 256 | throw _iteratorError; 257 | } 258 | } 259 | } 260 | } 261 | 262 | polygon += ''; 263 | return polygon; 264 | } 265 | /** 266 | * Converts an input geojson MultiPoint geometry to gml 267 | * @function 268 | * @param {Number[][]} coords the coordinates member of the geojson geometry 269 | * @param {String|Number} gmlId the gml:id 270 | * @param {Params} params optional parameters 271 | * @returns {String} a string containing gml representing the input geometry 272 | */ 273 | 274 | function multiPoint(coords, gmlId) { 275 | var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 276 | return multi('MultiPoint', 'pointMembers', point, coords, gmlId, params); 277 | } 278 | /** 279 | * Converts an input geojson MultiLineString geometry to gml 280 | * @function 281 | * @param {Number[][][]} coords the coordinates member of the geojson geometry 282 | * @param {String|Number} gmlId the gml:id 283 | * @param {Params} params optional parameters 284 | * @returns {String} a string containing gml representing the input geometry 285 | */ 286 | 287 | function multiLineString(coords, gmlId) { 288 | var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 289 | return multi('MultiCurve', 'curveMembers', lineString, coords, gmlId, params); 290 | } 291 | /** 292 | * Converts an input geojson MultiPolygon geometry to gml 293 | * @function 294 | * @param {Number[][][][]} coords the coordinates member of the geojson geometry 295 | * @param {String|Number} gmlId the gml:id 296 | * @param {Params} params optional parameters 297 | * @returns {String} a string containing gml representing the input geometry 298 | */ 299 | 300 | function multiPolygon(coords, gmlId) { 301 | var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 302 | return multi('MultiSurface', 'surfaceMembers', polygon, coords, gmlId, params); 303 | } 304 | /** 305 | * A helper to de-camelcase this module's geometry conversion methods 306 | * @param {Object} obj a mapping of camelcase geometry types to converter functions 307 | * @return {Object} a mapping of capitalized geometry types to converter functions 308 | * @example 309 | * makeConverter({lineString}) 310 | * // returns {LineString: lineString} 311 | */ 312 | 313 | function makeConverter(obj) { 314 | return Object.entries(obj).map(function (_ref) { 315 | var _ref2 = _slicedToArray(_ref, 2), 316 | type = _ref2[0], 317 | converter = _ref2[1]; 318 | 319 | return _defineProperty({}, type[0].toUpperCase() + type.slice(1), converter); 320 | }).reduce(function (a, b) { 321 | return Object.assign(a, b); 322 | }, {}); 323 | } 324 | /** 325 | * A helper to map geometry types to converter functions. 326 | * @function 327 | * @param {Object} obj an object mapping camelcase-d geometry type names to 328 | * converter functions for that type. 329 | * @example 330 | * import {point, lineString} from 'geojson-to-gml-3'; 331 | * const geomToGml = makeTranslator({point, lineString}); 332 | * geomToGml({type: 'Point', coordinates: [0, 0]}); 333 | */ 334 | 335 | 336 | function makeTranslator(obj) { 337 | var converter = makeConverter(obj); 338 | return function (geom, gmlId, params) { 339 | var _arguments = arguments; 340 | 341 | var warn = function warn() { 342 | return new Error("unkown: ".concat(geom.type, " ") + _toConsumableArray(_arguments).join()); 343 | }; 344 | 345 | var convert = converter[geom.type] || warn; 346 | return convert(geom.coordinates || geom.geometries, gmlId, params); 347 | }; 348 | } 349 | /** 350 | * a namespace to switch between geojson-handling functions by geojson.type 351 | * @const 352 | * @type {Object} 353 | */ 354 | 355 | var allTypes = makeConverter({ 356 | point: point, 357 | lineString: lineString, 358 | linearRing: linearRing, 359 | polygon: polygon, 360 | multiPoint: multiPoint, 361 | multiLineString: multiLineString, 362 | multiPolygon: multiPolygon 363 | }); 364 | /** 365 | * Converts an input geojson GeometryCollection geometry to gml 366 | * @function 367 | * @param {Object[]} coords the coordinates member of the geojson geometry 368 | * @param {String|Number} gmlId the gml:id 369 | * @param {Object} params optional parameters 370 | * @param {?String} params.srsName as string specifying SRS 371 | * @param {?Number|?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 372 | * @returns {String} a string containing gml representing the input geometry 373 | */ 374 | 375 | function geometryCollection(geoms, gmlId) { 376 | var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 377 | return multi('MultiGeometry', 'geometryMembers', allTypes, geoms, gmlId, params); 378 | } 379 | /** 380 | * Translates any geojson geometry into GML 3.2.1 381 | * @public 382 | * @function 383 | * @param {Object} geom a geojson geometry object 384 | * @param {?Array} geom.coordinates the nested array of coordinates forming the geometry 385 | * @param {?Object[]} geom.geometries for a GeometryCollection only, the array of member geometry objects 386 | * @param {String|Number} gmlId the gml:id of the geometry 387 | * @param {object} params optional parameters 388 | * @param {?String} params.srsName a string specifying the SRS 389 | * @param {?String} params.srsDimension the dimensionality of each coordinate, i.e. 2 or 3. 390 | * @param {?Number[]|?String[]} gmlIds an array of number/string gml:ids of the member geometries of a multigeometry. 391 | * @returns {String} a valid gml string describing the input geojson geometry 392 | */ 393 | 394 | var geomToGml = makeTranslator(Object.assign({ 395 | geometryCollection: geometryCollection 396 | }, allTypes)); 397 | 398 | /** 399 | * common checks, coersions, and informative errors/ warnings 400 | * @module ensure 401 | */ 402 | 403 | /** 404 | * Ensures the result is an array. 405 | * @private 406 | * @function 407 | * @param {Feature|Feature[]|FeatureCollection} maybe a GeoJSON 408 | * FeatureCollection, Feature, or an array of Features. 409 | * @return {Feature[]} 410 | */ 411 | var array = function array() { 412 | var _ref; 413 | 414 | for (var _len = arguments.length, maybe = new Array(_len), _key = 0; _key < _len; _key++) { 415 | maybe[_key] = arguments[_key]; 416 | } 417 | 418 | return (maybe[0].features || (_ref = []).concat.apply(_ref, maybe)).filter(Boolean); 419 | }; 420 | /** 421 | * Ensures a layer.id format of an input id. 422 | * @function 423 | * @param {String} lyr layer name 424 | * @param {String} id id, possibly already in correct layer.id format. 425 | * @return {String} a correctly-formatted gml:id 426 | */ 427 | 428 | var id$1 = function id(lyr, _id) { 429 | return /\./.exec(_id || '') ? _id : "".concat(lyr, ".").concat(_id); 430 | }; 431 | /** 432 | * return a correctly-formatted typeName 433 | * @function 434 | * @param {String} ns namespace 435 | * @param {String} layer layer name 436 | * @param {String} typeName typeName to check 437 | * @return {String} a correctly-formatted typeName 438 | * @throws {Error} if typeName it cannot form a typeName from ns and layer 439 | */ 440 | 441 | var typeName = function typeName(ns, layer, _typeName) { 442 | if (!_typeName && !(ns && layer)) { 443 | throw new Error("no typename possible: ".concat(JSON.stringify({ 444 | typeName: _typeName, 445 | ns: ns, 446 | layer: layer 447 | }, null, 2))); 448 | } 449 | 450 | return _typeName || "".concat(layer); 451 | }; // http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286 452 | 453 | /** 454 | * xml utilities. 455 | * @module xml 456 | */ 457 | 458 | /** 459 | * Turns an object into a string of xml attribute key-value pairs. 460 | * @function 461 | * @param {Object} attrs an object mapping attribute names to attribute values 462 | * @return {String} a string of xml attribute key-value pairs 463 | */ 464 | 465 | function attrs$1(attrs) { 466 | return Object.keys(attrs).map(function (a) { 467 | return attrs[a] ? " ".concat(a, "=\"").concat(escape(attrs[a]), "\"") : ''; 468 | }).join(''); 469 | } 470 | /** 471 | * Creates a string xml tag. 472 | * @function 473 | * @param {String} ns the tag's xml namespace abbreviation. 474 | * @param {String} tagName the tag name. 475 | * @param {Object} attrsObj @see xml.attrs. 476 | * @param {String} inner inner xml. 477 | * @return {String} an xml string. 478 | */ 479 | 480 | function tag(ns, tagName, attrsObj, inner) { 481 | var tag = (ns ? "".concat(ns, ":") : '') + tagName; 482 | 483 | if (tagName) { 484 | return "<".concat(tag).concat(attrs$1(attrsObj)).concat(inner !== null ? ">".concat(inner, ""); 485 | } else { 486 | throw new Error('no tag supplied ' + JSON.stringify({ 487 | ns: ns, 488 | tagName: tagName, 489 | attrsObj: attrsObj, 490 | inner: inner 491 | }, null, 2)); 492 | } 493 | } 494 | /** 495 | * Shorthand for creating a wfs xml tag. 496 | * @param {String} tagName a valid wfs tag name. 497 | * @param {Object} attrsObj @see xml.attrs. 498 | * @param {String} inner @see xml.tag. 499 | * @return {String} a wfs element. 500 | */ 501 | 502 | var wfs = function wfs(tagName, attrsObj, inner) { 503 | return tag('wfs', tagName, attrsObj, inner); 504 | }; 505 | /** 506 | * Creates a fes:ResourceId filter from a layername and id 507 | * @function 508 | * @param {String} lyr layer name of the filtered feature 509 | * @param {String} id feature id 510 | * @return {String} a filter-ecoding of the filter. 511 | */ 512 | 513 | var idFilter = function idFilter(lyr, id$$1) { 514 | return ""); 515 | }; 516 | /** 517 | * Creates an xml-safe string from a given input string 518 | * @function 519 | * @param {String} input String to escape 520 | * @return {String} XML-safe string 521 | */ 522 | 523 | function escape(input) { 524 | if (typeof input !== 'string') { 525 | // Backup check for non-strings 526 | return input; 527 | } 528 | 529 | var output = input.replace(/[<>&'"]/g, function (char) { 530 | switch (char) { 531 | case '<': 532 | return '<'; 533 | 534 | case '>': 535 | return '>'; 536 | 537 | case '&': 538 | return '&'; 539 | 540 | case "'": 541 | return '''; 542 | 543 | case '"': 544 | return '"'; 545 | } 546 | }); 547 | return output; 548 | } 549 | 550 | /* eslint-disable camelcase */ 551 | 552 | /** 553 | * Common utilities for handling parameters for creation of WFS trasactions. 554 | * @module utils 555 | */ 556 | 557 | /** 558 | * Iterates over the key-value pairs, filtering by a whitelist if available. 559 | * @function 560 | * @param {Array} whitelist a whitelist of property names 561 | * @param {Object} properties an object mapping property names to values 562 | * @param {Function} cb a function to call on each (whitelisted key, value) pair 563 | */ 564 | 565 | var useWhitelistIfAvailable = function useWhitelistIfAvailable(whitelist, properties, cb) { 566 | var _iteratorNormalCompletion = true; 567 | var _didIteratorError = false; 568 | var _iteratorError = undefined; 569 | 570 | try { 571 | for (var _iterator = (whitelist || Object.keys(properties))[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 572 | var prop = _step.value; 573 | var val = properties[prop]; 574 | 575 | if (Number.isNaN(val)) { 576 | throw new Error('NaN is not allowed.'); 577 | } 578 | 579 | if (val !== undefined) { 580 | cb(prop, val); 581 | } 582 | } 583 | } catch (err) { 584 | _didIteratorError = true; 585 | _iteratorError = err; 586 | } finally { 587 | try { 588 | if (!_iteratorNormalCompletion && _iterator.return != null) { 589 | _iterator.return(); 590 | } 591 | } finally { 592 | if (_didIteratorError) { 593 | throw _iteratorError; 594 | } 595 | } 596 | } 597 | }; 598 | var featureMembers = new Set(['properties', 'geometry', 'id', 'layer']); 599 | /** 600 | * Resolves attributes from feature, then params unless they are normally 601 | * found in the feature 602 | * @param {Object} feature a geojson feature 603 | * @param {Object} params an object of backup / override parameters 604 | * @param {Array} args parameter names to resolve from feature or 605 | * params 606 | * @return {Object} an object mapping each named parameter to its resolved 607 | * value 608 | */ 609 | 610 | function unpack(feature, params) { 611 | var results = {}; 612 | 613 | for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { 614 | args[_key - 2] = arguments[_key]; 615 | } 616 | 617 | for (var _i = 0; _i < args.length; _i++) { 618 | var arg = args[_i]; 619 | 620 | if (arg === 'layer') { 621 | results[arg] = (params.layer || {}).id || params.layer || (feature.layer || {}).id || feature.layer || ''; 622 | } else if (!featureMembers.has(arg)) { 623 | results[arg] = feature[arg] || params[arg] || ''; 624 | } else { 625 | results[arg] = params[arg] || feature[arg] || ''; 626 | } 627 | } 628 | 629 | return results; 630 | } 631 | /** 632 | * Generates an object to be passed to @see xml.attrs xmlns:ns="uri" definitions 633 | * for a wfs:Transaction 634 | * @param {Object} nsAssignments @see Params.nsAssignments 635 | * @param {String} xml arbitrary xml. 636 | * @return {Object} an object mapping each ns to its URI as 'xmlns:ns' : 'URI'. 637 | * @throws {Error} if any namespace used within `xml` is missing a URI 638 | * definition 639 | */ 640 | 641 | function generateNsAssignments(nsAssignments, xml) { 642 | var attrs = {}; 643 | 644 | var makeNsAssignment = function makeNsAssignment(ns, uri) { 645 | return attrs["xmlns:".concat(ns)] = uri; 646 | }; 647 | 648 | Object.keys(nsAssignments).forEach(function (ns) { 649 | makeNsAssignment(ns, nsAssignments[ns]); 650 | }); // check all ns's assigned 651 | 652 | var re = /(<|typeName=")(\w+):/g; 653 | var arr; 654 | var allNamespaces = new Set(); 655 | 656 | while ((arr = re.exec(xml)) !== null) { 657 | allNamespaces.add(arr[2]); 658 | } 659 | 660 | if (allNamespaces.has('fes')) { 661 | makeNsAssignment('fes', 'http://www.opengis.net/fes/2.0'); 662 | } 663 | makeNsAssignment('xsi', 'http://www.w3.org/2001/XMLSchema-instance'); 664 | makeNsAssignment('gml', 'http://www.opengis.net/gml/3.2'); 665 | makeNsAssignment('wfs', 'http://www.opengis.net/wfs/2.0'); 666 | var _iteratorNormalCompletion2 = true; 667 | var _didIteratorError2 = false; 668 | var _iteratorError2 = undefined; 669 | 670 | try { 671 | for (var _iterator2 = allNamespaces[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 672 | var ns = _step2.value; 673 | 674 | if (!attrs['xmlns:' + ns]) { 675 | throw new Error("unassigned namespace ".concat(ns)); 676 | } 677 | } 678 | /* , schemaLocations*/ 679 | 680 | } catch (err) { 681 | _didIteratorError2 = true; 682 | _iteratorError2 = err; 683 | } finally { 684 | try { 685 | if (!_iteratorNormalCompletion2 && _iterator2.return != null) { 686 | _iterator2.return(); 687 | } 688 | } finally { 689 | if (_didIteratorError2) { 690 | throw _iteratorError2; 691 | } 692 | } 693 | } 694 | 695 | return attrs; 696 | } 697 | /** 698 | * Returns a string alternating uri, whitespace, and the uri's schema's 699 | * location. 700 | * @param {Object} schemaLocations an object mapping uri:schemalocation 701 | * @return {string} a string that is a valid xsi:schemaLocation value. 702 | */ 703 | 704 | function generateSchemaLines() { 705 | var schemaLocations = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 706 | // TODO: add ns assignment check 707 | schemaLocations['http://www.opengis.net/wfs/2.0'] = 'http://schemas.opengis.net/wfs/2.0/wfs.xsd'; 708 | var schemaLines = []; 709 | Object.entries(schemaLocations).forEach(function (entry) { 710 | return schemaLines.push(entry.join('\n')); 711 | }); 712 | return schemaLines.join('\n'); 713 | } 714 | /** 715 | * Turns an array of geojson features into gml:_feature strings describing them. 716 | * @function 717 | * @param {Feature[]} features an array of features to translate to 718 | * gml:_features. 719 | * @param {Params} params an object of backup / override parameters 720 | * @return {String} a gml:_feature string. 721 | */ 722 | 723 | function translateFeatures(features) { 724 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 725 | var inner = ''; 726 | var srsName = params.srsName, 727 | srsDimension = params.srsDimension; 728 | var _iteratorNormalCompletion3 = true; 729 | var _didIteratorError3 = false; 730 | var _iteratorError3 = undefined; 731 | 732 | try { 733 | var _loop = function _loop() { 734 | var feature = _step3.value; 735 | 736 | // TODO: add whitelist support 737 | var _unpack = unpack(feature, params, 'ns', 'layer', 'geometry_name', 'properties', 'id', 'whitelist'), 738 | ns = _unpack.ns, 739 | layer = _unpack.layer, 740 | geometry_name = _unpack.geometry_name, 741 | properties = _unpack.properties, 742 | id$$1 = _unpack.id, 743 | whitelist = _unpack.whitelist; 744 | 745 | var fields = ''; 746 | 747 | if (geometry_name) { 748 | fields += tag(ns, geometry_name, {}, geomToGml(feature.geometry, '', { 749 | srsName: srsName, 750 | srsDimension: srsDimension 751 | })); 752 | } 753 | 754 | useWhitelistIfAvailable(whitelist, properties, function (prop, val) { 755 | if (val === null) { 756 | return fields; 757 | } 758 | 759 | return fields += tag(ns, prop, {}, escape(properties[prop])); 760 | }); 761 | inner += tag(ns, layer, { 762 | 'gml:id': id$1(layer, id$$1) 763 | }, fields); 764 | }; 765 | 766 | for (var _iterator3 = features[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 767 | _loop(); 768 | } 769 | } catch (err) { 770 | _didIteratorError3 = true; 771 | _iteratorError3 = err; 772 | } finally { 773 | try { 774 | if (!_iteratorNormalCompletion3 && _iterator3.return != null) { 775 | _iterator3.return(); 776 | } 777 | } finally { 778 | if (_didIteratorError3) { 779 | throw _iteratorError3; 780 | } 781 | } 782 | } 783 | 784 | return inner; 785 | } 786 | 787 | /** 788 | * Builds a filter from feature ids if one is not already input. 789 | * @function 790 | * @param {?String} filter a possible string filter 791 | * @param {Array} features an array of geojson feature objects 792 | * @param {Object} params an object of backup / override parameters 793 | * @return {String} A filter, or the input filter if it was a string. 794 | */ 795 | 796 | function filter(filter, features, params) { 797 | if (!filter) { 798 | filter = ''; 799 | var _iteratorNormalCompletion = true; 800 | var _didIteratorError = false; 801 | var _iteratorError = undefined; 802 | 803 | try { 804 | for (var _iterator = features[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 805 | var feature = _step.value; 806 | var layer = unpack(feature, params); 807 | filter += idFilter(layer, feature.id); 808 | } 809 | } catch (err) { 810 | _didIteratorError = true; 811 | _iteratorError = err; 812 | } finally { 813 | try { 814 | if (!_iteratorNormalCompletion && _iterator.return != null) { 815 | _iterator.return(); 816 | } 817 | } finally { 818 | if (_didIteratorError) { 819 | throw _iteratorError; 820 | } 821 | } 822 | } 823 | 824 | return "".concat(filter, ""); 825 | } else { 826 | return filter; 827 | } 828 | } 829 | 830 | /* eslint-disable camelcase, new-cap */ 831 | /** 832 | * Returns a wfs:Insert tag wrapping a translated feature 833 | * @function 834 | * @param {Feature[]|FeatureCollection|Feature} features Feature(s) to pass to 835 | * @see translateFeatures 836 | * @param {Params} params to be passed to @see translateFeatures, with optional 837 | * inputFormat, srsName, handle for the wfs:Insert tag. 838 | * @return {string} a wfs:Insert string. 839 | */ 840 | 841 | function Insert(features) { 842 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 843 | features = array(features); 844 | var inputFormat = params.inputFormat, 845 | srsName = params.srsName, 846 | handle = params.handle; 847 | 848 | if (!features.length) { 849 | console.warn('no features supplied'); 850 | return ''; 851 | } 852 | 853 | var toInsert = translateFeatures(features, params); 854 | return tag('wfs', 'Insert', { 855 | inputFormat: inputFormat, 856 | srsName: srsName, 857 | handle: handle 858 | }, toInsert); 859 | } 860 | /** 861 | * Updates the input features in bulk with params.properties or by id. 862 | * @param {Feature[]|FeatureCollection} features features to update. These may 863 | * pass in geometry_name, properties, and layer (overruled by params) and 864 | * ns, layer, srsName (overruling params). 865 | * @param {Params} params with optional properties, ns (namespace), layer, 866 | * geometry_name, filter, typeName, whitelist. 867 | * @return {string} a string wfs:Upate action. 868 | */ 869 | 870 | function Update(features) { 871 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 872 | features = array(features); 873 | /** 874 | * makes a wfs:Property string containg a wfs:ValueReference, wfs:Value pair. 875 | * @private 876 | * @function 877 | * @memberof Update~ 878 | * @param {string} prop the field/property name 879 | * @param {string} val the field/property value 880 | * @param {string} action one of 'insertBefore', 'insertAfter', 'remove', 881 | * 'replace'. See [OGC 09-025r2 § 15.2.5.2.1]{@link http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#286}. 882 | * `action` would delete or modify the order of fields within the remote 883 | * feature. There is currently no way to input `action,` since wfs:Update's 884 | * default action, 'replace', is sufficient. 885 | * @return {string} a wfs:Property(wfs:ValueReference) pair. 886 | */ 887 | 888 | var makeKvp = function makeKvp(prop, val, action$$1) { 889 | var value = ''; 890 | 891 | if (val === null) { 892 | value = wfs('Value', { 893 | 'xsi:nil': true 894 | }, ''); 895 | } else if (val !== undefined) { 896 | value = wfs('Value', {}, val); 897 | } 898 | 899 | return wfs('Property', {}, wfs('ValueReference', { 900 | action: action$$1 901 | }, prop) + value); 902 | }; 903 | 904 | if (params.properties) { 905 | var inputFormat = params.inputFormat, 906 | filter$$1 = params.filter, 907 | typeName$$1 = params.typeName, 908 | whitelist = params.whitelist; 909 | 910 | var _unpack = unpack(features[0] || {}, params, 'srsName', 'ns', 'layer', 'geometry_name'), 911 | srsName = _unpack.srsName, 912 | ns = _unpack.ns, 913 | layer = _unpack.layer, 914 | geometry_name = _unpack.geometry_name; 915 | 916 | typeName$$1 = typeName(ns, layer, typeName$$1); 917 | filter$$1 = filter(filter$$1, features, params); 918 | 919 | if (!filter$$1 && !features.length) { 920 | console.warn('neither features nor filter supplied'); 921 | return ''; 922 | } 923 | 924 | var fields = ''; 925 | useWhitelistIfAvailable( // TODO: action attr 926 | whitelist, params.properties, function (k, v) { 927 | return fields += makeKvp(k, escape(v)); 928 | }); 929 | 930 | if (geometry_name) { 931 | fields += makeKvp(geometry_name, tag(ns, geometry_name, {}, geomToGml(params.geometry, '', { 932 | srsName: srsName 933 | }))); 934 | } 935 | 936 | return wfs('Update', { 937 | inputFormat: inputFormat, 938 | srsName: srsName, 939 | typeName: typeName$$1 940 | }, fields + filter$$1); 941 | } else { 942 | // encapsulate each update in its own Update tag 943 | return features.map(function (f) { 944 | return Update(f, Object.assign({}, params, { 945 | properties: f.properties 946 | })); 947 | }).join(''); 948 | } 949 | } 950 | /** 951 | * Creates a wfs:Delete action, creating a filter and typeName from feature ids 952 | * if none are supplied. 953 | * @param {Feature[]|FeatureCollection|Feature} features 954 | * @param {Params} params optional parameter overrides. 955 | * @param {string} [params.ns] @see Params.ns 956 | * @param {string|Object} [params.layer] @see Params.layer 957 | * @param {string} [params.typeName] @see Params.typeName. This will be inferred 958 | * from feature/params layer and ns if this is left undefined. 959 | * @param {filter} [params.filter] @see Params.filter. This will be inferred 960 | * from feature ids and layer(s) if left undefined (@see ensureFilter). 961 | * @return {string} a wfs:Delete string. 962 | */ 963 | 964 | function Delete(features) { 965 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 966 | features = array(features); 967 | var filter$$1 = params.filter, 968 | typeName$$1 = params.typeName; // TODO: recur & encapsulate by typeName 969 | 970 | var _unpack2 = unpack(features[0] || {}, params, 'layer', 'ns'), 971 | ns = _unpack2.ns, 972 | layer = _unpack2.layer; 973 | 974 | typeName$$1 = typeName(ns, layer, typeName$$1); 975 | filter$$1 = filter(filter$$1, features, params); 976 | return wfs('Delete', { 977 | typeName: typeName$$1 978 | }, filter$$1); 979 | } 980 | /** 981 | * Returns a string wfs:Replace action. 982 | * @param {Feature[]|FeatureCollection|Feature} features feature(s) to replace 983 | * @param {Params} params with optional filter, inputFormat, srsName 984 | * @return {string} a string wfs:Replace action. 985 | */ 986 | 987 | function Replace(features) { 988 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 989 | features = array(features); 990 | 991 | var _unpack3 = unpack(features[0] || {}, params || {}, 'filter', 'inputFormat', 'srsName'), 992 | filter$$1 = _unpack3.filter, 993 | inputFormat = _unpack3.inputFormat, 994 | srsName = _unpack3.srsName; 995 | 996 | var replacements = translateFeatures([features[0]].filter(function (f) { 997 | return f; 998 | }), params || { 999 | srsName: srsName 1000 | }); 1001 | filter$$1 = filter(filter$$1, features, params); 1002 | return wfs('Replace', { 1003 | inputFormat: inputFormat, 1004 | srsName: srsName 1005 | }, replacements + filter$$1); 1006 | } 1007 | /** 1008 | * Wraps the input actions in a wfs:Transaction. 1009 | * @param {Object|string[]|string} actions an object mapping {Insert, Update, 1010 | * Delete} to feature(s) to pass to Insert, Update, Delete, or wfs:action 1011 | * string(s) to wrap in a transaction. 1012 | * @param {TransactionParams} params optional srsName, lockId, releaseAction, 1013 | * handle, inputFormat, version, and required nsAssignments, schemaLocations. 1014 | * @return {string} A wfs:transaction wrapping the input actions. 1015 | * @throws {Error} if `actions` is not an array of strings, a string, or 1016 | * {@see Insert, @see Update, @see Delete}, where each action are valid inputs 1017 | * to the eponymous function. 1018 | */ 1019 | 1020 | function Transaction(actions) { 1021 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1022 | var transactionParams = ['srsName', 'lockId', 'releaseAction', 'handle']; 1023 | var version = params.version, 1024 | _params$nsAssignments = params.nsAssignments, 1025 | nsAssignments = _params$nsAssignments === void 0 ? {} : _params$nsAssignments; // let converter = {Insert, Update, Delete}; 1026 | 1027 | var _ref = actions || {}, 1028 | toInsert = _ref.insert, 1029 | toUpdate = _ref.update, 1030 | toDelete = _ref.delete; 1031 | 1032 | var finalActions = ''; // processedActions would be more accurate 1033 | 1034 | if (Array.isArray(actions) && actions.every(function (v) { 1035 | return typeof v == 'string'; 1036 | })) { 1037 | finalActions += actions.join(''); 1038 | } else if (typeof actions == 'string') { 1039 | finalActions = actions; 1040 | } else if ([toInsert, toUpdate, toDelete].some(function (e) { 1041 | return e; 1042 | })) { 1043 | finalActions += Insert(toInsert, params) + Update(toUpdate, params) + Delete(toDelete, params); 1044 | } else { 1045 | throw new Error("unexpected input: ".concat(JSON.stringify(actions))); 1046 | } // generate schemaLocation, xmlns's 1047 | 1048 | 1049 | var attrs = generateNsAssignments(nsAssignments, actions); 1050 | attrs['xsi:schemaLocation'] = generateSchemaLines(params.schemaLocations); 1051 | attrs['service'] = 'WFS'; 1052 | attrs['version'] = /2\.0\.\d+/.exec(version || '') ? version : '2.0.0'; 1053 | transactionParams.forEach(function (param) { 1054 | if (params[param]) { 1055 | attrs[param] = params[param]; 1056 | } 1057 | }); 1058 | return wfs('Transaction', attrs, finalActions); 1059 | } 1060 | 1061 | exports.Insert = Insert; 1062 | exports.Update = Update; 1063 | exports.Delete = Delete; 1064 | exports.Replace = Replace; 1065 | exports.Transaction = Transaction; 1066 | --------------------------------------------------------------------------------