UNPKG

kml-utils

Version:

This lib is modified from @mapbox/togeojson and @mapbox/tokml, enhanced with folder capability

297 lines (270 loc) 8.82 kB
var strxml = require('strxml') var colorUtils = require('./utils/color') var toFeatureCollection = require('./convert/geojson').toFeatureCollection var geoUtils = require('./utils/geometry') var styleUtils = require('./utils/style') var DEFAULT_KML_STYLES = require('./constants') var isNum = require('./utils/type').isNum var isArr = require('./utils/type').isArr var isObj = require('./utils/type').isObj function tag(el, attributes, contents) { if (!isArr(attributes) && !isObj(attributes) && typeof attributes !== 'string') { attributes = '' } return strxml.tag(el, attributes, contents) } module.exports = function kmlify (geoJSON, folderTree, options) { options = Object.assign({ documentName: undefined, documentDescription: undefined, name: 'name', description: 'description', simpleStyle: true, timestamp: 'timestamp', folder: 'folder', dataType: 'geojson' // arcjson }, options) folderTree = folderTree || [] geoJSON = options.dataType === 'geojson' ? geoJSON : toFeatureCollection(geoJSON) return '<?xml version="1.0" encoding="UTF-8"?>' + tag('kml', { xmlns: 'http://www.opengis.net/kml/2.2' }, tag('Document', documentName(options) + documentDescription(options) + root(geoJSON, folderTree, options) ) ) } function root (geoJSON, folderTree, options) { if (!geoJSON.type) { return '' } var styleHashesArray = [] switch (geoJSON.type) { case 'FeatureCollection': if (!geoJSON.features) { return '' } if (folderTree.length > 0 && options.folder) { return folder(folderTree, geoJSON.features, styleHashesArray, options) } else { return geoJSON.features.map(feature(options, styleHashesArray)).join('') } case 'Feature': return feature(options, styleHashesArray)(geoJSON) default: return feature(options, styleHashesArray)({ type: 'Feature', geometry: geoJSON, 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', _[options.name]) : '' } function description(_, options) { return _[options.description] ? tag('description', _[options.description]) : '' } function timestamp (_, options) { return _[options.timestamp] ? tag('TimeStamp', tag('when', _[options.timestamp])) : '' } function folder (folderTree, features, styleHashesArray, options) { function _process (folders) { folders = folders || [] if (folders.length === 0) { return '' } return folders.map(function (folder) { var folderFeatures = features.filter(function (feature) { return feature.properties[options.folder] === folder.key }) var descTag = folder.desc?tag('desc',folder.desc):'' var valueTag = folder.value?tag('value',folder.value):'' return tag('Folder', tag('name', folder.name) + descTag + valueTag + _process(folder.children) + folderFeatures.map(feature(options, styleHashesArray)).join('') ) }).join('') } return _process(folderTree) } function feature (options, styleHashesArray) { return function(_) { if (!_.properties || !geoUtils.valid(_.geometry)) { return '' } var geometryString = any(_.geometry) if (!geometryString) { return '' } var styleDefinition = '' var styleReference = '' if (options.simpleStyle) { var styleHash = styleUtils.hashStyle(_.properties) if (styleHash) { if (geoUtils.isPoint(_.geometry) && styleUtils.hasMarkerStyle(_.properties)) { if (styleHashesArray.indexOf(styleHash) === -1) { styleDefinition = markerStyle(_.properties, styleHash) styleHashesArray.push(styleHash) } styleReference = tag('styleUrl', '#' + styleHash); } else if ( (geoUtils.isPolygon(_.geometry) || geoUtils.isLine(_.geometry)) && styleUtils.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 linearring (_) { return _.map(function (cds) { return cds.join(',') }).join(' ') } // ## Data function extendeddata (_) { return tag('ExtendedData', pairs(_).map(data).join('')) } function data (_) { return tag( 'Data', { name: _[0] }, tag( 'value', // 显示声明 attributes 为空对象,否则 tag 函数会在第二个参数不为 string 时,当 attributes 处理,会导致数字型的值处理成了 attributes,而 value 变成了 undefined // 比如:tag('value', 1) 会被处理成 tag('value', 1, undefined) {}, _[1] || '' ) ) } // ## Marker style function markerStyle (_, styleHash) { return tag('Style', { id: styleHash }, tag('IconStyle', tag('Icon', tag('href', styleUtils.iconUrl(_)) ) ) + iconSize(_) ) } function iconSize (_) { return tag('hotSpot', { xunits: 'fraction', yunits: 'fraction', x: 0.5, y: 0.5 }, '') } // ## Polygon and Line style function polygonAndLineStyle(_, styleHash) { var lineStyleList = [] var color = colorUtils.hexToKmlColor(_['stroke'], _['stroke-opacity']) || DEFAULT_KML_STYLES.lineStyleColor if (color) { lineStyleList.push(color) } var width = (isNum(_['stroke-width']) ? _['stroke-width']: DEFAULT_KML_STYLES.lineWidth) + '' if (width) { lineStyleList.push(width) } var lineStyle = '' if (lineStyleList.length) { lineStyle = tag('LineStyle', lineStyleList) } var polyStyle = '' if (_['fill'] || _['fill-opacity']) { polyStyle = tag('PolyStyle', [ tag('color', colorUtils.hexToKmlColor(_['fill'], _['fill-opacity']) || DEFAULT_KML_STYLES.polySyleColor) ]) } return tag('Style', { id: styleHash }, lineStyle + polyStyle) } // ## General helpers function pairs (_) { var o = [] for (var i in _) { o.push([i, _[i]]) } return o } // ## Geometry Types // // https://developers.google.com/kml/documentation/kmlreference#geometry var GeometryTypes = { 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] var inner = _.coordinates.slice(1) var outerRing = tag('outerBoundaryIs', tag('LinearRing', tag('coordinates', linearring(outer)))) var 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 GeometryTypes.Point({ coordinates: c }) }).join('')) }, MultiPolygon: function (_) { if (!_.coordinates.length) { return '' } return tag('MultiGeometry', _.coordinates.map(function (c) { return GeometryTypes.Polygon({ coordinates: c }) }).join('')) }, MultiLineString: function (_) { if (!_.coordinates.length) { return '' } return tag('MultiGeometry', _.coordinates.map(function (c) { return GeometryTypes.LineString({ coordinates: c }) }).join('')) }, GeometryCollection: function (_) { return tag('MultiGeometry', _.geometries.map(any).join('')) } } function any (_) { if (GeometryTypes[_.type]) { return GeometryTypes[_.type](_) } else { return '' } }