diff --git a/.gitignore b/.gitignore index c3c859c..d2a9f36 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ coverage/ -npm-debug.log \ No newline at end of file +npm-debug.log +.tap/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b5dc099..0000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -script: - - npm install - - npm test - - npm run cov diff --git a/CHANGELOG.md b/CHANGELOG.md index 215d3c8..c71d4e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.5.0 +* Includes abandoned/banished packages to allow for security updates + ## 0.4.0 * Adds support for simplestyle-spec. Thanks [Vincent Sels!](https://github.com/vincentsels) diff --git a/HELP.md b/HELP.md deleted file mode 100644 index 79fb147..0000000 --- a/HELP.md +++ /dev/null @@ -1,17 +0,0 @@ -tokml - -usage: - - tokml file.geojson - tokml < file.geojson > file.kml - - --simplestyle - enable simplestyle icon translation - --name - property for - --description - property for - --documentName - property for document - --documentDescription - property for document diff --git a/README.md b/README.md index c6851e8..1050405 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,14 @@ -[![Build Status](https://travis-ci.org/mapbox/tokml.png)](https://travis-ci.org/mapbox/tokml) [![Coverage Status](https://coveralls.io/repos/mapbox/tokml/badge.png)](https://coveralls.io/r/mapbox/tokml) - # tokml + Convert [GeoJSON](http://geojson.org/) to [KML](https://developers.google.com/kml/documentation/). ## Usage -with node/browserify - - npm install --save tokml - -otherwise: +with node - wget https://raw.github.com/mapbox/tokml/master/tokml.js + npm install --save https://github.com/SenteraLLC/tokml.git -as a binary: - - npm install -g tokml - tokml file.geojson > file.kml - tokml < file.geojson > file.kml ## Example @@ -78,11 +68,7 @@ for the full document. ## Development -Requires [node.js](http://nodejs.org/) and [browserify](https://github.com/substack/node-browserify): - -To build `tokml.js`: - - make +Requires [node.js](http://nodejs.org/) : To run tests: diff --git a/index.html b/index.html deleted file mode 100644 index e5fdcb0..0000000 --- a/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - tokml - - - - -

tokml

-

A module that converts GeoJSON to KML in Javascript.

-

demo

- - -
- - - - - diff --git a/index.js b/index.js index f08f16e..e003b90 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -var strxml = require('strxml'), +var strxml = require('./src/strxml.js'), tag = strxml.tag, encode = strxml.encode; diff --git a/package.json b/package.json index 7cab23f..385a944 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,14 @@ { "name": "tokml", - "version": "0.4.0", - "description": "convert geojson to kml", + "version": "0.5.0", + "description": "Convert geojson to kml", "main": "index.js", "scripts": { - "test": "tap test/*.js", - "build": "browserify -s tokml index.js > tokml.js", - "cov": "istanbul cover test/kml.test.js && coveralls < ./coverage/lcov.info" + "test": "tap test/*.js" }, "repository": { "type": "git", - "url": "https://github.com/mapbox/tokml.git" + "url": "https://github.com/SenteraLLC/tokml.git" }, "keywords": [ "kml", @@ -18,22 +16,13 @@ "geo", "maps" ], - "bin": { - "tokml": "tokml" - }, "author": "Tom MacWright", "license": "BSD-2-Clause", "devDependencies": { - "glob": "~3.2.6", - "corslite": "0.0.5", - "tap": "~0.4.8", - "fuzzer": "0.0.0", - "coveralls": "~2.10.0", - "istanbul": "~0.2.11" - }, - "dependencies": { - "minimist": "0.1.0", - "strxml": "0.0.0", - "rw": "0.0.4" + "glob": "13.0.1", + "random-js": "^2.1.0", + "tap": "^21.5.1", + "traverse": "^0.6.11", + "xtend": "^4.0.2" } } diff --git a/site/index.js b/site/index.js deleted file mode 100644 index 69ddfd0..0000000 --- a/site/index.js +++ /dev/null @@ -1,25 +0,0 @@ -var convert = document.getElementById('convert'), - convertRaw = document.getElementById('convert-raw'), - mapGeoJSON = document.getElementById('map-geojson'), - mapid = document.getElementById('map-id'), - xhr = require('corslite'), - saveAs = require('filesaver.js'), - tokml = require('../'); - -convert.onclick = function() { - xhr('http://api.tiles.mapbox.com/v3/' + mapid.value + '/markers.geojson', onload, true); - function onload(err, resp) { - if (err) return alert(err); - else return run(JSON.parse(resp.response)); - } -}; - -convertRaw.onclick = function() { - run(JSON.parse(mapGeoJSON.value)); -}; - -function run(gj) { - saveAs(new Blob([tokml(gj)], { - type: 'application/vnd.google-earth.kml+xml' - }), 'map.kml'); -} diff --git a/site/site.js b/site/site.js deleted file mode 100644 index b9ebfea..0000000 --- a/site/site.js +++ /dev/null @@ -1,469 +0,0 @@ -;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o' + - tag('kml', - tag('Document', - geojson.features.map(feature(options)).join('') - ), [['xmlns', 'http://www.opengis.net/kml/2.2']]); -}; - -function feature(options) { - return function(_) { - return tag('Placemark', - name(_.properties, options) + - description(_.properties, options) + - geometry.any(_.geometry) + - extendeddata(_.properties)); - }; -} - -function name(_, options) { - return (_[options.name]) ? tag('name', encode(_[options.name])) : ''; -} - -function description(_, options) { - return (_[options.description]) ? tag('description', encode(_[options.description])) : ''; -} - -// ## Geometry Types -// -// https://developers.google.com/kml/documentation/kmlreference#geometry -var geometry = { - Point: function(_) { - return tag('Point', tag('coordinates', _.coordinates.join(','))); - }, - LineString: function(_) { - return tag('LineString', tag('coordinates', linearring(_.coordinates))); - }, - Polygon: function(_) { - var outer = _.coordinates[0], - inner = _.coordinates.slice(1), - outerRing = tag('outerBoundaryIs', - tag('LinearRing', tag('coordinates', linearring(outer)))), - innerRings = inner.map(function(i) { - return tag('innerBoundaryIs', - tag('LinearRing', tag('coordinates', linearring(i)))); - }).join(''); - return tag('Polygon', outerRing + innerRings); - }, - MultiPoint: function(_) { - return tag('MultiGeometry', _.coordinates.map(function(c) { - return geometry.Point({ coordinates: c }); - }).join('')); - }, - MultiPolygon: function(_) { - return tag('MultiGeometry', _.coordinates.map(function(c) { - return geometry.Polygon({ coordinates: c }); - }).join('')); - }, - MultiLineString: function(_) { - return tag('MultiGeometry', _.coordinates.map(function(c) { - return geometry.LineString({ coordinates: c }); - }).join('')); - }, - GeometryCollection: function(_) { - return tag('MultiGeometry', - _.geometries.map(geometry.any).join('')); - }, - any: function(_) { - if (geometry[_.type]) { - return geometry[_.type](_); - } else { } - } -}; - -function linearring(_) { - return _.map(function(cds) { return cds.join(','); }).join(' '); -} - -// ## Data -function extendeddata(_) { - return tag('ExtendedData', pairs(_).map(data).join('')); -} - -function data(_) { - return tag('Data', encode(_[1]), [['name', encode(_[0])]]); -} - -// ## Helpers -function pairs(_) { - var o = []; - for (var i in _) o.push([i, _[i]]); - return o; -} - -function attr(_) { - return _ ? (' ' + _.map(function(a) { - return a[0] + '="' + a[1] + '"'; - }).join(' ')) : ''; -} - -function tag(el, contents, attributes) { - return '<' + el + attr(attributes) + '>' + contents + ''; -} - - -function encode(_) { - return _.replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); -} - -},{}],2:[function(require,module,exports){ -function xhr(url, callback, cors) { - var sent = false; - - if (typeof window.XMLHttpRequest === 'undefined') { - return callback(Error('Browser not supported')); - } - - if (typeof cors === 'undefined') { - var m = url.match(/^\s*https?:\/\/[^\/]*/); - cors = m && (m[0] !== location.protocol + '//' + location.domain + - (location.port ? ':' + location.port : '')); - } - - var x; - - function isSuccessful(status) { - return status >= 200 && status < 300 || status === 304; - } - - if (cors && ( - // IE7-9 Quirks & Compatibility - typeof window.XDomainRequest === 'object' || - // IE9 Standards mode - typeof window.XDomainRequest === 'function' - )) { - // IE8-10 - x = new window.XDomainRequest(); - - // Ensure callback is never called synchronously, i.e., before - // x.send() returns (this has been observed in the wild). - // See https://github.com/mapbox/mapbox.js/issues/472 - var original = callback; - callback = function() { - if (sent) { - original.apply(this, arguments); - } else { - var that = this, args = arguments; - setTimeout(function() { - original.apply(that, args); - }, 0); - } - } - } else { - x = new window.XMLHttpRequest(); - } - - function loaded() { - if ( - // XDomainRequest - x.status === undefined || - // modern browsers - isSuccessful(x.status)) callback.call(x, null, x); - else callback.call(x, x, null); - } - - // Both `onreadystatechange` and `onload` can fire. `onreadystatechange` - // has [been supported for longer](http://stackoverflow.com/a/9181508/229001). - if ('onload' in x) { - x.onload = loaded; - } else { - x.onreadystatechange = function readystate() { - if (x.readyState === 4) { - loaded(); - } - }; - } - - // Call the callback with the XMLHttpRequest object as an error and prevent - // it from ever being called again by reassigning it to `noop` - x.onerror = function error(evt) { - // XDomainRequest provides no evt parameter - callback.call(this, evt || true, null); - callback = function() { }; - }; - - // IE9 must have onprogress be set to a unique function. - x.onprogress = function() { }; - - x.ontimeout = function(evt) { - callback.call(this, evt, null); - callback = function() { }; - }; - - x.onabort = function(evt) { - callback.call(this, evt, null); - callback = function() { }; - }; - - // GET is the only supported HTTP Verb by XDomainRequest and is the - // only one supported here. - x.open('GET', url, true); - - // Send the request. Sending data is not supported. - x.send(null); - sent = true; - - return x; -} - -if (typeof module !== 'undefined') module.exports = xhr; - -},{}],3:[function(require,module,exports){ -/* FileSaver.js - * A saveAs() FileSaver implementation. - * 2013-01-23 - * - * By Eli Grey, http://eligrey.com - * License: X11/MIT - * See LICENSE.md - */ - -/*global self */ -/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, - plusplus: true */ - -/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ - -var saveAs = saveAs - || (navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator)) - || (function(view) { - "use strict"; - var - doc = view.document - // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet - , get_URL = function() { - return view.URL || view.webkitURL || view; - } - , URL = view.URL || view.webkitURL || view - , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") - , can_use_save_link = !view.externalHost && "download" in save_link - , click = function(node) { - var event = doc.createEvent("MouseEvents"); - event.initMouseEvent( - "click", true, false, view, 0, 0, 0, 0, 0 - , false, false, false, false, 0, null - ); - node.dispatchEvent(event); - } - , webkit_req_fs = view.webkitRequestFileSystem - , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem - , throw_outside = function (ex) { - (view.setImmediate || view.setTimeout)(function() { - throw ex; - }, 0); - } - , force_saveable_type = "application/octet-stream" - , fs_min_size = 0 - , deletion_queue = [] - , process_deletion_queue = function() { - var i = deletion_queue.length; - while (i--) { - var file = deletion_queue[i]; - if (typeof file === "string") { // file is an object URL - URL.revokeObjectURL(file); - } else { // file is a File - file.remove(); - } - } - deletion_queue.length = 0; // clear queue - } - , dispatch = function(filesaver, event_types, event) { - event_types = [].concat(event_types); - var i = event_types.length; - while (i--) { - var listener = filesaver["on" + event_types[i]]; - if (typeof listener === "function") { - try { - listener.call(filesaver, event || filesaver); - } catch (ex) { - throw_outside(ex); - } - } - } - } - , FileSaver = function(blob, name) { - // First try a.download, then web filesystem, then object URLs - var - filesaver = this - , type = blob.type - , blob_changed = false - , object_url - , target_view - , get_object_url = function() { - var object_url = get_URL().createObjectURL(blob); - deletion_queue.push(object_url); - return object_url; - } - , dispatch_all = function() { - dispatch(filesaver, "writestart progress write writeend".split(" ")); - } - // on any filesys errors revert to saving with object URLs - , fs_error = function() { - // don't create more object URLs than needed - if (blob_changed || !object_url) { - object_url = get_object_url(blob); - } - if (target_view) { - target_view.location.href = object_url; - } else { - window.open(object_url, "_blank"); - } - filesaver.readyState = filesaver.DONE; - dispatch_all(); - } - , abortable = function(func) { - return function() { - if (filesaver.readyState !== filesaver.DONE) { - return func.apply(this, arguments); - } - }; - } - , create_if_not_found = {create: true, exclusive: false} - , slice - ; - filesaver.readyState = filesaver.INIT; - if (!name) { - name = "download"; - } - if (can_use_save_link) { - object_url = get_object_url(blob); - save_link.href = object_url; - save_link.download = name; - click(save_link); - filesaver.readyState = filesaver.DONE; - dispatch_all(); - return; - } - // Object and web filesystem URLs have a problem saving in Google Chrome when - // viewed in a tab, so I force save with application/octet-stream - // http://code.google.com/p/chromium/issues/detail?id=91158 - if (view.chrome && type && type !== force_saveable_type) { - slice = blob.slice || blob.webkitSlice; - blob = slice.call(blob, 0, blob.size, force_saveable_type); - blob_changed = true; - } - // Since I can't be sure that the guessed media type will trigger a download - // in WebKit, I append .download to the filename. - // https://bugs.webkit.org/show_bug.cgi?id=65440 - if (webkit_req_fs && name !== "download") { - name += ".download"; - } - if (type === force_saveable_type || webkit_req_fs) { - target_view = view; - } - if (!req_fs) { - fs_error(); - return; - } - fs_min_size += blob.size; - req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) { - fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) { - var save = function() { - dir.getFile(name, create_if_not_found, abortable(function(file) { - file.createWriter(abortable(function(writer) { - writer.onwriteend = function(event) { - target_view.location.href = file.toURL(); - deletion_queue.push(file); - filesaver.readyState = filesaver.DONE; - dispatch(filesaver, "writeend", event); - }; - writer.onerror = function() { - var error = writer.error; - if (error.code !== error.ABORT_ERR) { - fs_error(); - } - }; - "writestart progress write abort".split(" ").forEach(function(event) { - writer["on" + event] = filesaver["on" + event]; - }); - writer.write(blob); - filesaver.abort = function() { - writer.abort(); - filesaver.readyState = filesaver.DONE; - }; - filesaver.readyState = filesaver.WRITING; - }), fs_error); - }), fs_error); - }; - dir.getFile(name, {create: false}, abortable(function(file) { - // delete file if it already exists - file.remove(); - save(); - }), abortable(function(ex) { - if (ex.code === ex.NOT_FOUND_ERR) { - save(); - } else { - fs_error(); - } - })); - }), fs_error); - }), fs_error); - } - , FS_proto = FileSaver.prototype - , saveAs = function(blob, name) { - return new FileSaver(blob, name); - } - ; - FS_proto.abort = function() { - var filesaver = this; - filesaver.readyState = filesaver.DONE; - dispatch(filesaver, "abort"); - }; - FS_proto.readyState = FS_proto.INIT = 0; - FS_proto.WRITING = 1; - FS_proto.DONE = 2; - - FS_proto.error = - FS_proto.onwritestart = - FS_proto.onprogress = - FS_proto.onwrite = - FS_proto.onabort = - FS_proto.onerror = - FS_proto.onwriteend = - null; - - view.addEventListener("unload", process_deletion_queue, false); - return saveAs; -}(self)); - -if (typeof module !== 'undefined') module.exports = saveAs; - -},{}],4:[function(require,module,exports){ -var convert = document.getElementById('convert'), - convertRaw = document.getElementById('convert-raw'), - mapGeoJSON = document.getElementById('map-geojson'), - mapid = document.getElementById('map-id'), - xhr = require('corslite'), - saveAs = require('filesaver.js'), - tokml = require('../'); - -convert.onclick = function() { - xhr('http://api.tiles.mapbox.com/v3/' + mapid.value + '/markers.geojson', onload, true); - function onload(err, resp) { - if (err) return alert(err); - else return run(JSON.parse(resp.response)); - } -}; - -convertRaw.onclick = function() { - run(JSON.parse(mapGeoJSON.value)); -}; - -function run(gj) { - saveAs(new Blob([tokml(gj)], { - type: 'application/vnd.google-earth.kml+xml' - }), 'map.kml'); -} - -},{"../":1,"corslite":2,"filesaver.js":3}]},{},[4]) -; \ No newline at end of file diff --git a/src/strxml.js b/src/strxml.js new file mode 100644 index 0000000..84bdca6 --- /dev/null +++ b/src/strxml.js @@ -0,0 +1,71 @@ +// The following is from https://github.com/miketheprogrammer/xml-escape/blob/master/index.js +// Which is published under the MIT License + +function escape(string, ignore) { + var pattern; + + if (string === null || string === undefined) return; + + ignore = (ignore || '').replace(/[^&"<>\']/g, ''); + pattern = '([&"<>\'])'.replace(new RegExp('[' + ignore + ']', 'g'), ''); + + return string.replace(new RegExp(pattern, 'g'), function(str, item) { + return escape.map[item]; + }) +} + +escape.map = { + '>': '>' + , '<': '<' + , "'": ''' + , '"': '"' + , '&': '&' +} + +// The following is from https://github.com/mapbox/strxml/blob/master/index.js +// Which is published under the BSD-2 License + +module.exports.attr = attr; +module.exports.tagClose = tagClose; +module.exports.tag = tag; +module.exports.encode = encode; + +/** + * @param {array} _ an array of attributes + * @returns {string} + */ +function attr(_) { + return (_ && _.length) ? (' ' + _.map(function(a) { + return a[0] + '="' + a[1] + '"'; + }).join(' ')) : ''; +} + +/** + * @param {string} el element name + * @param {array} attributes array of pairs + * @returns {string} + */ +function tagClose(el, attributes) { + return '<' + el + attr(attributes) + '/>'; +} + +/** + * @param {string} el element name + * @param {string} contents innerXML + * @param {array} attributes array of pairs + * @returns {string} + */ +function tag(el, contents, attributes) { + return '<' + el + attr(attributes) + '>' + contents + ''; +} + +/** + * @param {string} _ a string of attribute + * @returns {string} + */ +function encode(_) { + return (_ === null ? '' : _.toString()).replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} \ No newline at end of file diff --git a/test/fuzzer.js b/test/fuzzer.js new file mode 100644 index 0000000..6c25349 --- /dev/null +++ b/test/fuzzer.js @@ -0,0 +1,101 @@ +// The following code is from https://github.com/mapbox/fuzzer/blob/master/index.js + +xtend = require('xtend'), +traverse = require('traverse'); + + +const { Random, MersenneTwister19937 } = require('random-js'); + +var engine = MersenneTwister19937.seed(0); +var random = new Random(engine); + +/** + * @param _ {number} + * @returns {null} + */ +module.exports.seed = function(_) { + engine = MersenneTwister19937.seed(_) + random = new Random(engine); +}; + +module.exports.mutate = { + object: mutateObject, + string: mutateString +}; + +function mutateObject(obj) { + return function generate() { + // copy the object so that modifications + // are not additive + var copy = xtend({}, obj); + traverse(copy).forEach(transformObjectValue); + return copy; + }; +} + +function transformObjectValue(val) { + if (random.bool(0.1)) { + mutateVal.call(this, val); + } else if (random.bool(0.05)) { + if (this.level) { + this.remove(); + } + } +} + +function mutateVal(val) { + switch(typeof val) { + case 'boolean': + this.update(!val); + break; + case 'number': + this.update(val + random.real(-1000, 1000)); + break; + case 'string': + this.update(mutateString(val)); + break; + default: + if (Array.isArray(val)) this.update(mutateArray(val)); + break; + } +} + +/** + * @param val {string} a string value + * @returns {string} + */ +function mutateString(val) { + var arr = val.split(''); + if (random.bool(0.05)) { + arr = arr.reverse(); + } + if (random.bool(0.25)) { + arr.splice( + random.integer(1, arr.length), + random.integer(1, arr.length)); + } + if (random.bool(0.25)) { + var args = [random.integer(1, arr.length), 0] + .concat(random.string(random.integer(1, arr.length)).split('')); + arr.splice.apply(arr, args); + } + val = arr.join(''); + return val; +} + +/** + * @param val {array} an array + * @returns {array} + */ +function mutateArray(val) { + if (random.bool(0.1)) { + val = val.reverse(); + } + if (random.bool(0.05)) { + val = val.slice(random.integer(0, val.length)); + } + if (random.bool(0.05)) { + val = val.slice(0, random.integer(0, val.length)); + } + return val; +} \ No newline at end of file diff --git a/test/kml.test.js b/test/kml.test.js index d8053e1..32ff94b 100644 --- a/test/kml.test.js +++ b/test/kml.test.js @@ -1,7 +1,7 @@ var test = require('tap').test, fs = require('fs'), glob = require('glob'), - fuzzer = require('fuzzer'), + fuzzer = require('./fuzzer'), path = require('path'), tokml = require('../'); @@ -61,7 +61,7 @@ test('tokml', function(t) { tt.end(); }); - test('name & description', function(tt) { + t.test('name & description', function(tt) { geq(tt, 'name_desc'); geq(tt, 'document_name_desc', { documentName: 'Document Title', @@ -70,7 +70,7 @@ test('tokml', function(t) { tt.end(); }); - test('timestamp', function(tt) { + t.test('timestamp', function(tt) { geq(tt, 'timestamp', { name: 'name', description: 'description', @@ -79,7 +79,7 @@ test('tokml', function(t) { tt.end(); }); - test('simplestyle spec', function(tt) { + t.test('simplestyle spec', function(tt) { var options = { simplestyle: true }; geq(tt, 'simplestyle_optionnotset'); @@ -107,7 +107,7 @@ test('tokml', function(t) { tt.end(); }); - test('simplestyle hex to kml color conversion', function(tt) { + t.test('simplestyle hex to kml color conversion', function(tt) { testColor(tt, '#ff5500', 1, 'ff0055ff'); testColor(tt, '#0000ff', 1, 'ffff0000'); testColor(tt, '#00ff00', 1, 'ff00ff00'); @@ -136,7 +136,7 @@ test('tokml', function(t) { tt.end(); }); - test('fuzz', function(tt) { + t.test('fuzz', function(tt) { fuzzer.seed(0); glob.sync(__dirname + '/data/*.geojson').forEach(function(gj) { var generator = fuzzer.mutate.object(JSON.parse(fs.readFileSync(gj))); @@ -151,6 +151,7 @@ test('tokml', function(t) { }); tt.end(); }); + t.end(); }); function file(f) { diff --git a/tokml b/tokml deleted file mode 100755 index 567bf2b..0000000 --- a/tokml +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node - -var tokml = require('./'), - rw = require('rw'), - argv = require('minimist')(process.argv.slice(2), { - boolean: 'simplestyle' - }); - -if (process.stdin.isTTY && !argv._[0]) { - process.stdout.write(rw.readSync(__dirname + '/HELP.md')); - process.exit(1); -} - -var input = rw.readSync(argv._.length ? argv._[0] : '/dev/stdin', 'utf8'); -process.stdout.write(tokml(JSON.parse(input), argv)); diff --git a/tokml.js b/tokml.js deleted file mode 100644 index 2377b7e..0000000 --- a/tokml.js +++ /dev/null @@ -1,343 +0,0 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.tokml = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o' + - tag('kml', - tag('Document', - documentName(options) + - documentDescription(options) + - root(geojson, options) - ), [['xmlns', 'http://www.opengis.net/kml/2.2']]); -}; - -function feature(options, styleHashesArray) { - return function(_) { - if (!_.properties || !geometry.valid(_.geometry)) return ''; - var geometryString = geometry.any(_.geometry); - if (!geometryString) return ''; - - var styleDefinition = '', - styleReference = ''; - if (options.simplestyle) { - var styleHash = hashStyle(_.properties); - if (styleHash) { - if (geometry.isPoint(_.geometry) && hasMarkerStyle(_.properties)) { - if (styleHashesArray.indexOf(styleHash) === -1) { - styleDefinition = markerStyle(_.properties, styleHash); - styleHashesArray.push(styleHash); - } - styleReference = tag('styleUrl', '#' + styleHash); - } else if ((geometry.isPolygon(_.geometry) || geometry.isLine(_.geometry)) && - hasPolygonAndLineStyle(_.properties)) { - if (styleHashesArray.indexOf(styleHash) === -1) { - styleDefinition = polygonAndLineStyle(_.properties, styleHash); - styleHashesArray.push(styleHash); - } - styleReference = tag('styleUrl', '#' + styleHash); - } - // Note that style of GeometryCollection / MultiGeometry is not supported - } - } - - return styleDefinition + tag('Placemark', - name(_.properties, options) + - description(_.properties, options) + - extendeddata(_.properties) + - timestamp(_.properties, options) + - geometryString + - styleReference); - }; -} - -function root(_, options) { - if (!_.type) return ''; - var styleHashesArray = []; - - switch (_.type) { - case 'FeatureCollection': - if (!_.features) return ''; - return _.features.map(feature(options, styleHashesArray)).join(''); - case 'Feature': - return feature(options, styleHashesArray)(_); - default: - return feature(options, styleHashesArray)({ - type: 'Feature', - geometry: _, - properties: {} - }); - } -} - -function documentName(options) { - return (options.documentName !== undefined) ? tag('name', options.documentName) : ''; -} - -function documentDescription(options) { - return (options.documentDescription !== undefined) ? tag('description', options.documentDescription) : ''; -} - -function name(_, options) { - return _[options.name] ? tag('name', encode(_[options.name])) : ''; -} - -function description(_, options) { - return _[options.description] ? tag('description', encode(_[options.description])) : ''; -} - -function timestamp(_, options) { - return _[options.timestamp] ? tag('TimeStamp', tag('when', encode(_[options.timestamp]))) : ''; -} - -// ## Geometry Types -// -// https://developers.google.com/kml/documentation/kmlreference#geometry -var geometry = { - Point: function(_) { - return tag('Point', tag('coordinates', _.coordinates.join(','))); - }, - LineString: function(_) { - return tag('LineString', tag('coordinates', linearring(_.coordinates))); - }, - Polygon: function(_) { - if (!_.coordinates.length) return ''; - var outer = _.coordinates[0], - inner = _.coordinates.slice(1), - outerRing = tag('outerBoundaryIs', - tag('LinearRing', tag('coordinates', linearring(outer)))), - innerRings = inner.map(function(i) { - return tag('innerBoundaryIs', - tag('LinearRing', tag('coordinates', linearring(i)))); - }).join(''); - return tag('Polygon', outerRing + innerRings); - }, - MultiPoint: function(_) { - if (!_.coordinates.length) return ''; - return tag('MultiGeometry', _.coordinates.map(function(c) { - return geometry.Point({ coordinates: c }); - }).join('')); - }, - MultiPolygon: function(_) { - if (!_.coordinates.length) return ''; - return tag('MultiGeometry', _.coordinates.map(function(c) { - return geometry.Polygon({ coordinates: c }); - }).join('')); - }, - MultiLineString: function(_) { - if (!_.coordinates.length) return ''; - return tag('MultiGeometry', _.coordinates.map(function(c) { - return geometry.LineString({ coordinates: c }); - }).join('')); - }, - GeometryCollection: function(_) { - return tag('MultiGeometry', - _.geometries.map(geometry.any).join('')); - }, - valid: function(_) { - return _ && _.type && (_.coordinates || - _.type === 'GeometryCollection' && _.geometries && _.geometries.every(geometry.valid)); - }, - any: function(_) { - if (geometry[_.type]) { - return geometry[_.type](_); - } else { - return ''; - } - }, - isPoint: function(_) { - return _.type === 'Point' || - _.type === 'MultiPoint'; - }, - isPolygon: function(_) { - return _.type === 'Polygon' || - _.type === 'MultiPolygon'; - }, - isLine: function(_) { - return _.type === 'LineString' || - _.type === 'MultiLineString'; - } -}; - -function linearring(_) { - return _.map(function(cds) { return cds.join(','); }).join(' '); -} - -// ## Data -function extendeddata(_) { - return tag('ExtendedData', pairs(_).map(data).join('')); -} - -function data(_) { - return tag('Data', tag('value', encode(_[1])), [['name', encode(_[0])]]); -} - -// ## Marker style -function hasMarkerStyle(_) { - return !!(_['marker-size'] || _['marker-symbol'] || _['marker-color']); -} - -function markerStyle(_, styleHash) { - return tag('Style', - tag('IconStyle', - tag('Icon', - tag('href', iconUrl(_)))) + - iconSize(_), [['id', styleHash]]); -} - -function iconUrl(_) { - var size = _['marker-size'] || 'medium', - symbol = _['marker-symbol'] ? '-' + _['marker-symbol'] : '', - color = (_['marker-color'] || '7e7e7e').replace('#', ''); - - return 'https://api.tiles.mapbox.com/v3/marker/' + 'pin-' + size.charAt(0) + - symbol + '+' + color + '.png'; -} - -function iconSize(_) { - return tag('hotSpot', '', [ - ['xunits', 'fraction'], - ['yunits', 'fraction'], - ['x', 0.5], - ['y', 0.5] - ]); -} - -// ## Polygon and Line style -function hasPolygonAndLineStyle(_) { - for (var key in _) { - if ({ - "stroke": true, - "stroke-opacity": true, - "stroke-width": true, - "fill": true, - "fill-opacity": true - }[key]) return true; - } -} - -function polygonAndLineStyle(_, styleHash) { - var lineStyle = tag('LineStyle', [ - tag('color', hexToKmlColor(_['stroke'], _['stroke-opacity']) || 'ff555555') + - tag('width', _['stroke-width'] === undefined ? 2 : _['stroke-width']) - ]); - - var polyStyle = ''; - - if (_['fill'] || _['fill-opacity']) { - polyStyle = tag('PolyStyle', [ - tag('color', hexToKmlColor(_['fill'], _['fill-opacity']) || '88555555') - ]); - } - - return tag('Style', lineStyle + polyStyle, [['id', styleHash]]); -} - -// ## Style helpers -function hashStyle(_) { - var hash = ''; - - if (_['marker-symbol']) hash = hash + 'ms' + _['marker-symbol']; - if (_['marker-color']) hash = hash + 'mc' + _['marker-color'].replace('#', ''); - if (_['marker-size']) hash = hash + 'ms' + _['marker-size']; - if (_['stroke']) hash = hash + 's' + _['stroke'].replace('#', ''); - if (_['stroke-width']) hash = hash + 'sw' + _['stroke-width'].toString().replace('.', ''); - if (_['stroke-opacity']) hash = hash + 'mo' + _['stroke-opacity'].toString().replace('.', ''); - if (_['fill']) hash = hash + 'f' + _['fill'].replace('#', ''); - if (_['fill-opacity']) hash = hash + 'fo' + _['fill-opacity'].toString().replace('.', ''); - - return hash; -} - -function hexToKmlColor(hexColor, opacity) { - if (typeof hexColor !== 'string') return ''; - - hexColor = hexColor.replace('#', '').toLowerCase(); - - if (hexColor.length === 3) { - hexColor = hexColor[0] + hexColor[0] + - hexColor[1] + hexColor[1] + - hexColor[2] + hexColor[2]; - } else if (hexColor.length !== 6) { - return ''; - } - - var r = hexColor[0] + hexColor[1]; - var g = hexColor[2] + hexColor[3]; - var b = hexColor[4] + hexColor[5]; - - var o = 'ff'; - if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) { - o = (opacity * 255).toString(16); - if (o.indexOf('.') > -1) o = o.substr(0, o.indexOf('.')); - if (o.length < 2) o = '0' + o; - } - - return o + b + g + r; -} - -// ## General helpers -function pairs(_) { - var o = []; - for (var i in _) o.push([i, _[i]]); - return o; -} -},{"strxml":2}],2:[function(require,module,exports){ -module.exports.attr = attr; -module.exports.tagClose = tagClose; -module.exports.tag = tag; -module.exports.encode = encode; - -/** - * @param {array} _ an array of attributes - * @returns {string} - */ -function attr(_) { - return (_ && _.length) ? (' ' + _.map(function(a) { - return a[0] + '="' + a[1] + '"'; - }).join(' ')) : ''; -} - -/** - * @param {string} el element name - * @param {array} attributes array of pairs - * @returns {string} - */ -function tagClose(el, attributes) { - return '<' + el + attr(attributes) + '/>'; -} - -/** - * @param {string} el element name - * @param {string} contents innerXML - * @param {array} attributes array of pairs - * @returns {string} - */ -function tag(el, contents, attributes) { - return '<' + el + attr(attributes) + '>' + contents + ''; -} - -/** - * @param {string} _ a string of attribute - * @returns {string} - */ -function encode(_) { - return (_ === null ? '' : _.toString()).replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); -} - -},{}]},{},[1])(1) -}); \ No newline at end of file