@tmcw/togeojson
Version:
convert KML and GPX to GeoJSON
3 lines (2 loc) • 15.4 kB
JavaScript
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).toGeoJSON={})}(this,(function(e){"use strict";function t(e,t){return Array.from(e.getElementsByTagName(t))}function n(e){return"#"===e[0]?e:`#${e}`}function o(e){return e?.normalize(),e?.textContent||""}function r(e,t,n){const o=e.getElementsByTagName(t),r=o.length?o[0]:null;return r&&n&&n(r),r}function i(e,t,n){const o={};if(!e)return o;const r=e.getElementsByTagName(t),i=r.length?r[0]:null;return i&&n?n(i,o):o}function s(e,t,n){const i=o(r(e,t));return i&&n&&n(i)||{}}function c(e,t,n){const i=Number.parseFloat(o(r(e,t)));if(!Number.isNaN(i))return i&&n&&n(i)||{}}function l(e,t,n){const i=Number.parseFloat(o(r(e,t)));if(!Number.isNaN(i))return n&&n(i),i}function u(e,t){const n={};for(const o of t)s(e,o,(e=>{n[o]=e}));return n}function a(e){return 1===e?.nodeType}function f(e){let t=[];if(null===e)return t;for(const n of Array.from(e.childNodes)){if(!a(n))continue;const e=p(n.nodeName);if("gpxtpx:TrackPointExtension"===e)t=t.concat(f(n));else{const r=o(n);t.push([e,m(r)])}}return t}function p(e){return["heart","gpxtpx:hr","hr"].includes(e)?"heart":e}function m(e){const t=Number.parseFloat(e);return Number.isNaN(t)?e:t}function g(e){const t=[Number.parseFloat(e.getAttribute("lon")||""),Number.parseFloat(e.getAttribute("lat")||"")];if(Number.isNaN(t[0])||Number.isNaN(t[1]))return null;l(e,"ele",(e=>{t.push(e)}));const n=r(e,"time");return{coordinates:t,time:n?o(n):null,extendedValues:f(r(e,"extensions"))}}function d(e){return i(e,"line",(e=>Object.assign({},s(e,"color",(e=>({stroke:`#${e}`}))),c(e,"opacity",(e=>({"stroke-opacity":e}))),c(e,"width",(e=>({"stroke-width":96*e/25.4}))))))}function h(e,n){const r=u(n,["name","cmt","desc","type","time","keywords"]);for(const[t,i]of e)for(const e of Array.from(n.getElementsByTagNameNS(i,"*")))r[e.tagName.replace(":","_")]=o(e)?.trim();const i=t(n,"link");return i.length&&(r.links=i.map((e=>Object.assign({href:e.getAttribute("href")},u(e,["text","type"]))))),r}function y(e,n){const o=t(e,n),r=[],i=[],s={};for(let e=0;e<o.length;e++){const t=g(o[e]);if(t){r.push(t.coordinates),t.time&&i.push(t.time);for(const[n,r]of t.extendedValues){const t="heart"===n?n:`${n.replace("gpxtpx:","")}s`;s[t]||(s[t]=Array(o.length).fill(null)),s[t][e]=r}}}if(!(r.length<2))return{line:r,times:i,extendedValues:s}}function b(e,t){const n=y(t,"rtept");if(n)return{type:"Feature",properties:Object.assign({_gpxType:"rte"},h(e,t),d(r(t,"extensions"))),geometry:{type:"LineString",coordinates:n.line}}}function N(e,n){const o=t(n,"trkseg"),i=[],s=[],c=[];for(const e of o){const t=y(e,"trkpt");t&&(c.push(t),t.times?.length&&s.push(t.times))}if(0===c.length)return null;const l=c.length>1,u=Object.assign({_gpxType:"trk"},h(e,n),d(r(n,"extensions")),s.length?{coordinateProperties:{times:l?s:s[0]}}:{});for(let e=0;e<c.length;e++){const t=c[e];i.push(t.line),u.coordinateProperties||(u.coordinateProperties={});const n=u.coordinateProperties;for(const[o,r]of Object.entries(t.extendedValues))l?(n[o]||(n[o]=c.map((e=>new Array(e.line.length).fill(null)))),n[o][e]=r):n[o]=r}return{type:"Feature",properties:u,geometry:l?{type:"MultiLineString",coordinates:i}:{type:"LineString",coordinates:i[0]}}}function x(e,t){const n=Object.assign(h(e,t),u(t,["sym"])),o=g(t);return o?{type:"Feature",properties:n,geometry:{type:"Point",coordinates:o.coordinates}}:null}function*A(e){const n=e,o="http://www.garmin.com/xmlschemas/GpxExtensions/v3",r=[["gpxx",o]],i=n.getElementsByTagName("gpx")[0]?.attributes;if(i)for(const e of Array.from(i))e.name?.startsWith("xmlns:")&&e.value!==o&&r.push([e.name,e.value]);for(const e of t(n,"trk")){const t=N(r,e);t&&(yield t)}for(const e of t(n,"rte")){const t=b(r,e);t&&(yield t)}for(const e of t(n,"wpt")){const t=x(r,e);t&&(yield t)}}const T=[["heartRate","heartRates"],["Cadence","cadences"],["Speed","speeds"],["Watts","watts"]],O=[["TotalTimeSeconds","totalTimeSeconds"],["DistanceMeters","distanceMeters"],["MaximumSpeed","maxSpeed"],["AverageHeartRateBpm","avgHeartRate"],["MaximumHeartRateBpm","maxHeartRate"],["AvgSpeed","avgSpeed"],["AvgWatts","avgWatts"],["MaxWatts","maxWatts"]];function k(e,t){const n=[];for(const[i,s]of t){let t=r(e,i);if(!t){const n=e.getElementsByTagNameNS("http://www.garmin.com/xmlschemas/ActivityExtension/v2",i);n.length&&(t=n[0])}const c=Number.parseFloat(o(t));Number.isNaN(c)||n.push([s,c])}return n}function L(e){const t=[l(e,"LongitudeDegrees"),l(e,"LatitudeDegrees")];if(void 0===t[0]||Number.isNaN(t[0])||void 0===t[1]||Number.isNaN(t[1]))return null;const n=r(e,"HeartRateBpm"),i=o(r(e,"Time"));return r(e,"AltitudeMeters",(e=>{const n=Number.parseFloat(o(e));Number.isNaN(n)||t.push(n)})),{coordinates:t,time:i||null,heartRate:n?Number.parseFloat(o(n)):null,extensions:k(e,T)}}function v(e){const n=t(e,"Trackpoint"),o=[],r=[],i=[];if(n.length<2)return null;const s={},c={extendedProperties:s};for(let e=0;e<n.length;e++){const t=L(n[e]);if(null===t)continue;o.push(t.coordinates);const{time:c,heartRate:l,extensions:u}=t;c&&r.push(c),l&&i.push(l);for(const[t,o]of u)s[t]||(s[t]=Array(n.length).fill(null)),s[t][e]=o}return o.length<2?null:Object.assign(c,{line:o,times:r,heartRates:i})}function S(e){const n=t(e,"Track"),r=[],s=[],c=[],l=[];let u;const a=Object.assign(Object.fromEntries(k(e,O)),i(e,"Name",(e=>({name:o(e)}))));for(const e of n)u=v(e),u&&(r.push(u.line),u.times.length&&s.push(u.times),u.heartRates.length&&c.push(u.heartRates),l.push(u.extendedProperties));for(let e=0;e<l.length;e++){const t=l[e];for(const o in t)1===n.length?u&&(a[o]=u.extendedProperties[o]):(a[o]||(a[o]=r.map((e=>Array(e.length).fill(null)))),a[o][e]=t[o])}return 0===r.length?null:((s.length||c.length)&&(a.coordinateProperties=Object.assign(s.length?{times:1===r.length?s[0]:s}:{},c.length?{heart:1===r.length?c[0]:c}:{})),{type:"Feature",properties:a,geometry:1===r.length?{type:"LineString",coordinates:r[0]}:{type:"MultiLineString",coordinates:r}})}function*E(e){for(const n of t(e,"Lap")){const e=S(n);e&&(yield e)}for(const n of t(e,"Courses")){const e=S(n);e&&(yield e)}}function F(e,t){const n={},o="stroke"===t||"fill"===t?t:`${t}-color`;return"#"===e[0]&&(e=e.substring(1)),6===e.length||3===e.length?n[o]=`#${e}`:8===e.length&&(n[`${t}-opacity`]=Number.parseInt(e.substring(0,2),16)/255,n[o]=`#${e.substring(6,8)}${e.substring(4,6)}${e.substring(2,4)}`),n}function R(e,t,n){const o={};return l(e,t,(e=>{o[n]=e})),o}function P(e,t){return i(e,"color",(e=>F(o(e),t)))}function w(e){return i(e,"Icon",((e,t)=>(s(e,"href",(e=>{t.icon=e})),t)))}function M(e){return Object.assign({},function(e){return i(e,"PolyStyle",((e,t)=>Object.assign(t,i(e,"color",(e=>F(o(e),"fill"))),s(e,"fill",(e=>{if("0"===e)return{"fill-opacity":0}})),s(e,"outline",(e=>{if("0"===e)return{"stroke-opacity":0}})))))}(e),function(e){return i(e,"LineStyle",(e=>Object.assign(P(e,"stroke"),R(e,"width","stroke-width"))))}(e),function(e){return i(e,"LabelStyle",(e=>Object.assign(P(e,"label"),R(e,"scale","label-scale"))))}(e),function(e){return i(e,"IconStyle",(e=>Object.assign(P(e,"icon"),R(e,"scale","icon-scale"),R(e,"heading","icon-heading"),i(e,"hotSpot",(e=>{const t=Number.parseFloat(e.getAttribute("x")||""),n=Number.parseFloat(e.getAttribute("y")||""),o=e.getAttribute("xunits")||"",r=e.getAttribute("yunits")||"";return Number.isNaN(t)||Number.isNaN(n)?{}:{"icon-offset":[t,n],"icon-offset-units":[o,r]}})),w(e))))}(e))}const _=/\s*/g,G=/^\s*|\s*$/g,j=/\s+/;function B(e){return e.replace(_,"").split(",").map(Number.parseFloat).filter((e=>!Number.isNaN(e))).slice(0,3)}function C(e){return e.replace(G,"").split(j).map(B).filter((e=>e.length>=2))}function U(e){let n=t(e,"coord");var r,i,s;0===n.length&&(r=e,i="coord",s="*",n=Array.from(r.getElementsByTagNameNS(s,i)));const c=n.map((e=>o(e).split(" ").map(Number.parseFloat)));return 0===c.length?null:{geometry:c.length>2?{type:"LineString",coordinates:c}:{type:"Point",coordinates:c[0]},times:t(e,"when").map((e=>o(e)))}}function D(e){if(0===e.length)return e;const t=e[0],n=e[e.length-1];let o=!0;for(let e=0;e<Math.max(t.length,n.length);e++)if(t[e]!==n[e]){o=!1;break}return o?e:e.concat([e[0]])}function V(e){return o(r(e,"coordinates"))}function I(e){let n=[],o=[];for(let r=0;r<e.childNodes.length;r++){const i=e.childNodes.item(r);if(a(i))switch(i.tagName){case"MultiGeometry":case"MultiTrack":case"gx:MultiTrack":{const e=I(i);n=n.concat(e.geometries),o=o.concat(e.coordTimes);break}case"Point":{const e=B(V(i));e.length>=2&&n.push({type:"Point",coordinates:e});break}case"LinearRing":case"LineString":{const e=C(V(i));e.length>=2&&n.push({type:"LineString",coordinates:e});break}case"Polygon":{const e=[];for(const n of t(i,"LinearRing")){const t=D(C(V(n)));t.length>=4&&e.push(t)}e.length&&n.push({type:"Polygon",coordinates:e});break}case"Track":case"gx:Track":{const e=U(i);if(!e)break;const{times:t,geometry:r}=e;n.push(r),t.length&&o.push(t);break}}}return{geometries:n,coordTimes:o}}const $=e=>Number(e),W={string:e=>e,int:$,uint:$,short:$,ushort:$,float:$,double:$,bool:e=>Boolean(e)};function H(e,n){return i(e,"ExtendedData",((e,i)=>{for(const n of t(e,"Data"))i[n.getAttribute("name")||""]=o(r(n,"value"));for(const r of t(e,"SimpleData")){const e=r.getAttribute("name")||"",t=n[e]||W.string;i[e]=t(o(r))}return i}))}function Q(e){const t=r(e,"description");for(const e of Array.from(t?.childNodes||[]))if(4===e.nodeType)return{description:{"@type":"html",value:o(e)}};return{}}function q(e){return i(e,"TimeSpan",(e=>({timespan:{begin:o(r(e,"begin")),end:o(r(e,"end"))}})))}function z(e){return i(e,"TimeStamp",(e=>({timestamp:o(r(e,"when"))})))}function J(e,t){return s(e,"styleUrl",(e=>(e=n(e),t[e]?Object.assign({styleUrl:e},t[e]):{styleUrl:e})))}var K;function X(e){if(r(e,"gx:LatLonQuad")){return{geometry:{type:"Polygon",coordinates:[D(C(V(e)))]}}}return function(e){const t=r(e,"LatLonBox");if(t){const e=l(t,"north"),n=l(t,"west"),o=l(t,"east"),r=l(t,"south"),i=l(t,"rotation");if("number"==typeof e&&"number"==typeof r&&"number"==typeof n&&"number"==typeof o){const t=[n,r,o,e];let s=[[[n,e],[o,e],[o,r],[n,r],[n,e]]];return"number"==typeof i&&(s=function(e,t,n){const o=[(e[0]+e[2])/2,(e[1]+e[3])/2];return[t[0].map((e=>{const t=e[1]-o[1],r=e[0]-o[0],i=Math.sqrt(t**2+r**2),s=Math.atan2(t,r)+n*Y;return[o[0]+Math.cos(s)*i,o[1]+Math.sin(s)*i]}))]}(t,s,i)),{bbox:t,geometry:{type:"Polygon",coordinates:s}}}}return null}(e)}!function(e){e.ABSOLUTE="absolute",e.RELATIVE_TO_GROUND="relativeToGround",e.CLAMP_TO_GROUND="clampToGround",e.CLAMP_TO_SEAFLOOR="clampToSeaFloor",e.RELATIVE_TO_SEAFLOOR="relativeToSeaFloor"}(K||(K={}));const Y=Math.PI/180;function Z(e,t,n,o){const r=X(e),i=r?.geometry||null;if(!i&&o.skipNullGeometry)return null;const s={type:"Feature",geometry:i,properties:Object.assign({"@geometry-type":"groundoverlay"},u(e,["name","address","visibility","open","phoneNumber","description"]),Q(e),J(e,t),M(e),w(e),H(e,n),q(e),z(e))};r?.bbox&&(s.bbox=r.bbox),void 0!==s.properties?.visibility&&(s.properties.visibility="0"!==s.properties.visibility);const c=e.getAttribute("id");return null!==c&&""!==c&&(s.id=c),s}function ee(e){const t=r(e,"Lod");return t?[l(t,"minLodPixels")??-1,l(t,"maxLodPixels")??-1,l(t,"minFadeExtent")??null,l(t,"maxFadeExtent")??null]:null}function te(e){const t=r(e,"LatLonAltBox");if(t){const e=l(t,"north"),n=l(t,"west"),o=l(t,"east"),i=l(t,"south");if(function(e){switch(e?.textContent){case K.ABSOLUTE:return K.ABSOLUTE;case K.CLAMP_TO_GROUND:return K.CLAMP_TO_GROUND;case K.CLAMP_TO_SEAFLOOR:return K.CLAMP_TO_SEAFLOOR;case K.RELATIVE_TO_GROUND:return K.RELATIVE_TO_GROUND;case K.RELATIVE_TO_SEAFLOOR:return K.RELATIVE_TO_SEAFLOOR}return null}(r(t,"altitudeMode")||r(t,"gx:altitudeMode"))&&console.debug("Encountered an unsupported feature of KML for togeojson: please contact developers for support of altitude mode."),"number"==typeof e&&"number"==typeof i&&"number"==typeof n&&"number"==typeof o){return{bbox:[n,i,o,e],geometry:{type:"Polygon",coordinates:[[[n,e],[o,e],[o,i],[n,i],[n,e]]]}}}}return null}function ne(e){const t=r(e,"Link");return t?u(t,["href","refreshMode","refreshInterval","viewRefreshMode","viewRefreshTime","viewBoundScale","viewFormat","httpQuery"]):{}}function oe(e,t,n,o){const i=function(e){const t=r(e,"Region");return t?{coordinateBox:te(t),lod:ee(e)}:null}(e),s=i?.coordinateBox?.geometry||null;if(!s&&o.skipNullGeometry)return null;const c={type:"Feature",geometry:s,properties:Object.assign({"@geometry-type":"networklink"},u(e,["name","address","visibility","open","phoneNumber","styleUrl","refreshVisibility","flyToView","description"]),Q(e),J(e,t),M(e),w(e),H(e,n),q(e),z(e),ne(e),i?.lod?{lod:i.lod}:{})};i?.coordinateBox?.bbox&&(c.bbox=i.coordinateBox.bbox),void 0!==c.properties?.visibility&&(c.properties.visibility="0"!==c.properties.visibility);const l=e.getAttribute("id");return null!==l&&""!==l&&(c.id=l),c}function re(e,t,n,o){const{coordTimes:r,geometries:i}=I(e),s=function(e){return 0===e.length?null:1===e.length?e[0]:{type:"GeometryCollection",geometries:e}}(i);if(!s&&o.skipNullGeometry)return null;const c={type:"Feature",geometry:s,properties:Object.assign(u(e,["name","address","visibility","open","phoneNumber","description"]),Q(e),J(e,t),M(e),H(e,n),q(e),z(e),r.length?{coordinateProperties:{times:1===r.length?r[0]:r}}:{})};void 0!==c.properties?.visibility&&(c.properties.visibility="0"!==c.properties.visibility);const l=e.getAttribute("id");return null!==l&&""!==l&&(c.id=l),c}function ie(e){let t=e.getAttribute("id");const o=e.parentNode;return!t&&a(o)&&"CascadingStyle"===o.localName&&(t=o.getAttribute("kml:id")||o.getAttribute("id")),n(t||"")}function se(e){const o={};for(const n of t(e,"Style"))o[ie(n)]=M(n);for(const r of t(e,"StyleMap")){const e=n(r.getAttribute("id")||"");s(r,"styleUrl",(t=>{t=n(t),o[t]&&(o[e]=o[t])}))}return o}function ce(e){const n={};for(const o of t(e,"SimpleField"))n[o.getAttribute("name")||""]=W[o.getAttribute("type")||""]||W.string;return n}const le=["name","visibility","open","address","description","phoneNumber","visibility"];function*ue(e,n={skipNullGeometry:!1}){const o=e,r=se(o),i=ce(o);for(const e of t(o,"Placemark")){const t=re(e,r,i,n);t&&(yield t)}for(const e of t(o,"GroundOverlay")){const t=Z(e,r,i,n);t&&(yield t)}for(const e of t(o,"NetworkLink")){const t=oe(e,r,i,n);t&&(yield t)}}e.gpx=function(e){return{type:"FeatureCollection",features:Array.from(A(e))}},e.gpxGen=A,e.kml=function(e,t={skipNullGeometry:!1}){return{type:"FeatureCollection",features:Array.from(ue(e,t))}},e.kmlGen=ue,e.kmlWithFolders=function(e,t={skipNullGeometry:!1}){const n=e,r=se(n),i=ce(n),s={type:"root",children:[]};return function e(t,n,s){if(a(t))switch(t.tagName){case"GroundOverlay":{const e=Z(t,r,i,s);e&&n.children.push(e);break}case"Placemark":{const e=re(t,r,i,s);e&&n.children.push(e);break}case"Folder":{const e=function(e){const t={};for(const n of Array.from(e.childNodes))a(n)&&le.includes(n.tagName)&&(t[n.tagName]=o(n));return{type:"folder",meta:t,children:[]}}(t);n.children.push(e),n=e;break}case"NetworkLink":{const e=oe(t,r,i,s);e&&n.children.push(e);break}}if(t.childNodes)for(let o=0;o<t.childNodes.length;o++)e(t.childNodes[o],n,s)}(n,s,t),s},e.tcx=function(e){return{type:"FeatureCollection",features:Array.from(E(e))}},e.tcxGen=E}));
//# sourceMappingURL=togeojson.umd.js.map