diff --git a/README.md b/README.md index 9b43f75..be49a84 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,10 @@ Converts OSM data into GeoJSON. * `data`: the OSM data. Either as a XML DOM or in [OSM JSON](http://overpass-api.de/output_formats.html#json). * `options`: optional. The following options can be used: * `flatProperties`: If true, the resulting GeoJSON feature's properties will be a simple key-value list instead of a structured json object (with separate tags and metadata). default: false + * `wayRefs`: If true, the GeoJSON will have a `ndrefs` property when flat, or in `meta` when not flat, which is an array of all the node members of the `way`. default: false * `uninterestingTags`: Either a [blacklist](https://github.com/tyrasd/osmtogeojson/blob/2.0.0/index.js#L14-L24) of tag keys or a callback function. Will be used to decide if a feature is *interesting* enough for its own GeoJSON feature. * `polygonFeatures`: Either a [json object](https://github.com/tyrasd/osmtogeojson/blob/2.0.0/polygon_features.json) or callback function that is used to determine if a closed way should be treated as a Polygon or LineString. [read more](https://wiki.openstreetmap.org/wiki/Overpass_turbo/Polygon_Features) + * `mapRelations` either `true` or `false`. If set to true, osmtogeojson returns some additional data, apart from the GeoJSON. The data returned will now be an object containing `geojson`, `featuresInRelation` and `nodes`. `geojson` will be the current geojson format. `featuresInRelation` is an array of feature ids that are members of relations. `nodes` is an array of node objects, including nodes without tags. Additionally, if `mapRelations` is passed, geometry processing of relations is ignored, and relations are omitted from the `geojson` output. The result is a javascript object of GeoJSON data: diff --git a/index.js b/index.js index 17d18d7..9834663 100644 --- a/index.js +++ b/index.js @@ -37,6 +37,8 @@ osmtogeojson = function( data, options, featureCallback ) { { verbose: false, flatProperties: true, + wayRefs: false, + mapRelations: false, uninterestingTags: { "source": true, "source_ref": true, @@ -232,6 +234,8 @@ osmtogeojson = function( data, options, featureCallback ) { var nodes = new Array(); var ways = new Array(); var rels = new Array(); + var featuresInRelation = new Set(); + // helper function function copy_attribute( x, o, attr ) { if (x.hasAttribute(attr)) @@ -372,8 +376,9 @@ osmtogeojson = function( data, options, featureCallback ) { copy_attribute( node, nodeObject, 'changeset' ); copy_attribute( node, nodeObject, 'uid' ); copy_attribute( node, nodeObject, 'user' ); - if (!_.isEmpty(tags)) - nodeObject.tags = tags; + // always set nodeObject.tags to tags, even if tags is an empty object. + // this ensures we get valid properties when returning data for all nodes + nodeObject.tags = tags; nodes.push(nodeObject); }); // ways @@ -417,45 +422,61 @@ osmtogeojson = function( data, options, featureCallback ) { _.each( xml.getElementsByTagName('relation'), function( relation, i ) { var tags = {}; var members = []; - _.each( relation.getElementsByTagName('tag'), function( tag ) { - tags[tag.getAttribute('k')] = tag.getAttribute('v'); - }); + var has_full_geometry = false; _.each( relation.getElementsByTagName('member'), function( member, i ) { members[i] = {}; + copy_attribute( member, members[i], 'ref' ); copy_attribute( member, members[i], 'role' ); copy_attribute( member, members[i], 'type' ); + if (!has_full_geometry && (members[i].type == 'node' && member.getAttribute('lat')) || (members[i].type == 'way' && member.getElementsByTagName('nd').length>0) ) has_full_geometry = true; + if (options.mapRelations) { + featuresInRelation.add(`${members[i].type}/${members[i].ref}`); + } }); - var relObject = { - "type": "relation" + + if (!options.mapRelations) { + _.each( relation.getElementsByTagName('tag'), function( tag ) { + tags[tag.getAttribute('k')] = tag.getAttribute('v'); + }); + var relObject = { + "type": "relation" + } + copy_attribute( relation, relObject, 'id' ); + copy_attribute( relation, relObject, 'version' ); + copy_attribute( relation, relObject, 'timestamp' ); + copy_attribute( relation, relObject, 'changeset' ); + copy_attribute( relation, relObject, 'uid' ); + copy_attribute( relation, relObject, 'user' ); + if (members.length > 0) + relObject.members = members; + if (!_.isEmpty(tags)) + relObject.tags = tags; + if (centroid = relation.getElementsByTagName('center')[0]) + centerGeometry(relObject,centroid); + if (has_full_geometry) + fullGeometryRelation(relObject, relation.getElementsByTagName('member')); + else if (bounds = relation.getElementsByTagName('bounds')[0]) + boundsGeometry(relObject,bounds); + rels.push(relObject); } - copy_attribute( relation, relObject, 'id' ); - copy_attribute( relation, relObject, 'version' ); - copy_attribute( relation, relObject, 'timestamp' ); - copy_attribute( relation, relObject, 'changeset' ); - copy_attribute( relation, relObject, 'uid' ); - copy_attribute( relation, relObject, 'user' ); - if (members.length > 0) - relObject.members = members; - if (!_.isEmpty(tags)) - relObject.tags = tags; - if (centroid = relation.getElementsByTagName('center')[0]) - centerGeometry(relObject,centroid); - if (has_full_geometry) - fullGeometryRelation(relObject, relation.getElementsByTagName('member')); - else if (bounds = relation.getElementsByTagName('bounds')[0]) - boundsGeometry(relObject,bounds); - rels.push(relObject); }); - return _convert2geoJSON(nodes,ways,rels); + if (options.mapRelations) { + return { + geojson: _convert2geoJSON(nodes,ways,rels), + featuresInRelation: Array.from(featuresInRelation), + nodes: nodes + } + } else { + return _convert2geoJSON(nodes,ways,rels); + } } function _convert2geoJSON(nodes,ways,rels) { - // helper function that checks if there are any tags other than "created_by", "source", etc. or any tag provided in ignore_tags function has_interesting_tags(t, ignore_tags) { if (typeof ignore_tags !== "object") @@ -475,7 +496,8 @@ osmtogeojson = function( data, options, featureCallback ) { "version": object.version, "changeset": object.changeset, "user": object.user, - "uid": object.uid + "uid": object.uid, + "ndrefs": object.hasOwnProperty('ndrefs') && options.wayRefs ? object.ndrefs : undefined }; for (var k in res) if (res[k] === undefined) @@ -510,12 +532,14 @@ osmtogeojson = function( data, options, featureCallback ) { var waynids = new Object(); for (var i=0;i(t.version||0)?e:t:o.merge(e,t)}var o=e("./lodash.custom.js"),a=e("geojson-rewind"),i={};e("osm-polygon-features").forEach(function(e){if("all"===e.polygon)i[e.key]=!0;else{var t="whitelist"===e.polygon?"included_values":"excluded_values",n={};e.values.forEach(function(e){n[e]=!0}),i[e.key]={},i[e.key][t]=n}});var u={};u=function(e,t){function n(e){function t(e){var t=o.clone(e);t.lat=e.center.lat,t.lon=e.center.lon,t.__is_center_placeholder=!0,i.push(t)}function n(e){function t(e,t,r){var o={type:"node",id:"_"+n.type+"/"+n.id+"bounds"+r,lat:e,lon:t};n.nodes.push(o.id),i.push(o)}var n=o.clone(e);n.nodes=[],t(n.bounds.minlat,n.bounds.minlon,1),t(n.bounds.maxlat,n.bounds.minlon,2),t(n.bounds.maxlat,n.bounds.maxlon,3),t(n.bounds.minlat,n.bounds.maxlon,4),n.nodes.push(n.nodes[0]),n.__is_bounds_placeholder=!0,u.push(n)}function r(e){function t(e,t,n){var r={type:"node",id:n,lat:e,lon:t};i.push(r)}o.isArray(e.nodes)||(e.nodes=e.geometry.map(function(e){return null!==e?"_anonymous@"+e.lat+"/"+e.lon:"_anonymous@unknown_location"})),e.geometry.forEach(function(n,r){n&&t(n.lat,n.lon,e.nodes[r])})}function a(e){function t(e,t,n){var r={type:"node",id:n,lat:e,lon:t};i.push(r)}function n(e,t){function n(e,t){var n={type:"node",id:"_anonymous@"+e+"/"+t,lat:e,lon:t};r.nodes.push(n.id),i.push(n)}if(!u.some(function(e){return"way"==e.type&&e.id==t})){var r={type:"way",id:t,nodes:[]};e.forEach(function(e){e?n(e.lat,e.lon):r.nodes.push(void 0)}),u.push(r)}}e.members.forEach(function(e,r){"node"==e.type?e.lat&&t(e.lat,e.lon,e.ref):"way"==e.type&&e.geometry&&(e.ref="_fullGeom"+e.ref,n(e.geometry,e.ref))})}for(var i=new Array,u=new Array,l=new Array,c=0;c0});y.center&&t(y),d?a(y):y.bounds&&n(y)}return s(i,u,l)}function u(e){function t(e,t,n){e.hasAttribute(n)&&(t[n]=e.getAttribute(n))}function n(e,n){var r=o.clone(e);t(n,r,"lat"),t(n,r,"lon"),r.__is_center_placeholder=!0,u.push(r)}function r(e,t){function n(e,t,n){var o={type:"node",id:"_"+r.type+"/"+r.id+"bounds"+n,lat:e,lon:t};r.nodes.push(o.id),u.push(o)}var r=o.clone(e);r.nodes=[],n(t.getAttribute("minlat"),t.getAttribute("minlon"),1),n(t.getAttribute("maxlat"),t.getAttribute("minlon"),2),n(t.getAttribute("maxlat"),t.getAttribute("maxlon"),3),n(t.getAttribute("minlat"),t.getAttribute("maxlon"),4),r.nodes.push(r.nodes[0]),r.__is_bounds_placeholder=!0,l.push(r)}function a(e,t){function n(e,t,n){var r={type:"node",id:n,lat:e,lon:t};return u.push(r),r.id}o.isArray(e.nodes)||(e.nodes=[],o.each(t,function(t,n){e.nodes.push("_anonymous@"+t.getAttribute("lat")+"/"+t.getAttribute("lon"))})),o.each(t,function(t,r){t.getAttribute("lat")&&n(t.getAttribute("lat"),t.getAttribute("lon"),e.nodes[r])})}function i(e,t){function n(e,t,n){var r={type:"node",id:n,lat:e,lon:t};u.push(r)}function r(e,t){function n(e,t){var n={type:"node",id:"_anonymous@"+e+"/"+t,lat:e,lon:t};r.nodes.push(n.id),u.push(n)}if(!l.some(function(e){return"way"==e.type&&e.id==t})){var r={type:"way",id:t,nodes:[]};o.each(e,function(e){e.getAttribute("lat")?n(e.getAttribute("lat"),e.getAttribute("lon")):r.nodes.push(void 0)}),l.push(r)}}o.each(t,function(t,o){"node"==e.members[o].type?t.getAttribute("lat")&&n(t.getAttribute("lat"),t.getAttribute("lon"),e.members[o].ref):"way"==e.members[o].type&&t.getElementsByTagName("nd").length>0&&(e.members[o].ref="_fullGeom"+e.members[o].ref,r(t.getElementsByTagName("nd"),e.members[o].ref))})}var u=new Array,l=new Array,c=new Array;o.each(e.getElementsByTagName("node"),function(e,n){var r={};o.each(e.getElementsByTagName("tag"),function(e){r[e.getAttribute("k")]=e.getAttribute("v")});var a={type:"node"};t(e,a,"id"),t(e,a,"lat"),t(e,a,"lon"),t(e,a,"version"),t(e,a,"timestamp"),t(e,a,"changeset"),t(e,a,"uid"),t(e,a,"user"),o.isEmpty(r)||(a.tags=r),u.push(a)});var f,p;return o.each(e.getElementsByTagName("way"),function(e,i){var u={},s=[];o.each(e.getElementsByTagName("tag"),function(e){u[e.getAttribute("k")]=e.getAttribute("v")});var c=!1;o.each(e.getElementsByTagName("nd"),function(e,t){var n;(n=e.getAttribute("ref"))&&(s[t]=n),!c&&e.getAttribute("lat")&&(c=!0)});var y={type:"way"};t(e,y,"id"),t(e,y,"version"),t(e,y,"timestamp"),t(e,y,"changeset"),t(e,y,"uid"),t(e,y,"user"),s.length>0&&(y.nodes=s),o.isEmpty(u)||(y.tags=u),(f=e.getElementsByTagName("center")[0])&&n(y,f),c?a(y,e.getElementsByTagName("nd")):(p=e.getElementsByTagName("bounds")[0])&&r(y,p),l.push(y)}),o.each(e.getElementsByTagName("relation"),function(e,a){var u={},s=[];o.each(e.getElementsByTagName("tag"),function(e){u[e.getAttribute("k")]=e.getAttribute("v")});var l=!1;o.each(e.getElementsByTagName("member"),function(e,n){s[n]={},t(e,s[n],"ref"),t(e,s[n],"role"),t(e,s[n],"type"),(!l&&"node"==s[n].type&&e.getAttribute("lat")||"way"==s[n].type&&e.getElementsByTagName("nd").length>0)&&(l=!0)});var y={type:"relation"};t(e,y,"id"),t(e,y,"version"),t(e,y,"timestamp"),t(e,y,"changeset"),t(e,y,"uid"),t(e,y,"user"),s.length>0&&(y.members=s),o.isEmpty(u)||(y.tags=u),(f=e.getElementsByTagName("center")[0])&&n(y,f),l?i(y,e.getElementsByTagName("member")):(p=e.getElementsByTagName("bounds")[0])&&r(y,p),c.push(y)}),s(u,l,c)}function s(e,n,r){function i(e,n){if("object"!=typeof n&&(n={}),"function"==typeof t.uninterestingTags)return!t.uninterestingTags(e,n);for(var r in e)if(t.uninterestingTags[r]!==!0&&n[r]!==!0&&n[r]!==e[r])return!0;return!1}function u(e){var t={timestamp:e.timestamp,version:e.version,changeset:e.changeset,user:e.user,uid:e.uid};for(var n in t)void 0===t[n]&&delete t[n];return t}function s(e,n){function r(e){for(var n,r,o,a,i,u,s=function(e){return e[0]},f=function(e){return e[e.length-1]},p=[];e.length;)for(n=e.pop().nodes.slice(),p.push(n);e.length&&s(n)!==f(n);){for(r=s(n),o=f(n),a=0;ar!=c>r&&n<(l-u)*(r-s)/(c-s)+u;f&&(o=!o)}return o};for(e=o(e),t=0;t-1}function N(e,t){var n=this.__data__,r=J(n,e);return r<0?n.push([e,t]):n[r][1]=t,this}function P(e){var t=-1,n=e?e.length:0;for(this.clear();++t1?n[o-1]:Tt,i=o>2?n[2]:Tt;for(a=e.length>3&&"function"==typeof a?(o--,a):Tt,i&&Xe(n[0],n[1],i)&&(a=o<3?Tt:a,o=1),t=Object(t);++ru))return!1;var c=a.get(e);if(c&&a.get(t))return c==t;var f=-1,p=!0,y=o&St?new D:Tt;for(a.set(e,t),a.set(t,e);++f-1&&e%1==0&&e-1&&e%1==0&&e<=Dt}function dt(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function gt(e){return!!e&&"object"==typeof e}function vt(e){if(!gt(e)||$n.call(e)!=Xt||d(e))return!1;var t=qn(e);if(null===t)return!0;var n=Rn.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&Dn.call(n)==Un}function ht(e){return"symbol"==typeof e||gt(e)&&$n.call(e)==Yt}function bt(e){return Te(e,jt(e))}function mt(e){return null==e?"":be(e)}function _t(e,t,n){var r=null==e?Tt:ee(e,t);return r===Tt?n:r}function wt(e,t){return null!=e&&Ge(e,t,re)}function At(e){return lt(e)?V(e):ce(e)}function jt(e){return lt(e)?V(e,!0):fe(e)}function kt(e){return e}function Ot(e){return le("function"==typeof e?e:Q(e,!0))}function Et(e){return He(e)?c(tt(e)):ve(e)}function xt(){return[]}function Mt(){return!1}var Tt,Nt="4.15.0",Pt=200,Bt="Expected a function",Ft="__lodash_hash_undefined__",St=1,Lt=2,It=1/0,Dt=9007199254740991,Rt="[object Arguments]",Ut="[object Array]",$t="[object Boolean]",Ct="[object Date]",Gt="[object Error]",zt="[object Function]",Wt="[object GeneratorFunction]",qt="[object Map]",Vt="[object Number]",Xt="[object Object]",Ht="[object Promise]",Jt="[object RegExp]",Kt="[object Set]",Qt="[object String]",Yt="[object Symbol]",Zt="[object WeakMap]",en="[object ArrayBuffer]",tn="[object DataView]",nn="[object Float32Array]",rn="[object Float64Array]",on="[object Int8Array]",an="[object Int16Array]",un="[object Int32Array]",sn="[object Uint8Array]",ln="[object Uint8ClampedArray]",cn="[object Uint16Array]",fn="[object Uint32Array]",pn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,yn=/^\w*$/,dn=/^\./,gn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,vn=/[\\^$.*+?()[\]{}|]/g,hn=/\\(\\)?/g,bn=/\w*$/,mn=/^\[object .+?Constructor\]$/,_n=/^(?:0|[1-9]\d*)$/,wn={};wn[nn]=wn[rn]=wn[on]=wn[an]=wn[un]=wn[sn]=wn[ln]=wn[cn]=wn[fn]=!0,wn[Rt]=wn[Ut]=wn[en]=wn[$t]=wn[tn]=wn[Ct]=wn[Gt]=wn[zt]=wn[qt]=wn[Vt]=wn[Xt]=wn[Jt]=wn[Kt]=wn[Qt]=wn[Zt]=!1;var An={};An[Rt]=An[Ut]=An[en]=An[tn]=An[$t]=An[Ct]=An[nn]=An[rn]=An[on]=An[an]=An[un]=An[qt]=An[Vt]=An[Xt]=An[Jt]=An[Kt]=An[Qt]=An[Yt]=An[sn]=An[ln]=An[cn]=An[fn]=!0,An[Gt]=An[zt]=An[Zt]=!1;var jn="object"==typeof e&&e&&e.Object===Object&&e,kn="object"==typeof self&&self&&self.Object===Object&&self,On=jn||kn||Function("return this")(),En="object"==typeof n&&n&&!n.nodeType&&n,xn=En&&"object"==typeof t&&t&&!t.nodeType&&t,Mn=xn&&xn.exports===En,Tn=Mn&&jn.process,Nn=function(){try{return Tn&&Tn.binding("util")}catch(e){}}(),Pn=Nn&&Nn.isTypedArray,Bn=Array.prototype,Fn=Function.prototype,Sn=Object.prototype,Ln=On["__core-js_shared__"],In=function(){var e=/[^.]+$/.exec(Ln&&Ln.keys&&Ln.keys.IE_PROTO||"");return e?"Symbol(src)_1."+e:""}(),Dn=Fn.toString,Rn=Sn.hasOwnProperty,Un=Dn.call(Object),$n=Sn.toString,Cn=RegExp("^"+Dn.call(Rn).replace(vn,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Gn=Mn?On.Buffer:Tt,zn=On.Symbol,Wn=On.Uint8Array,qn=v(Object.getPrototypeOf,Object),Vn=Object.create,Xn=Sn.propertyIsEnumerable,Hn=Bn.splice,Jn=Object.getOwnPropertySymbols,Kn=Gn?Gn.isBuffer:Tt,Qn=v(Object.keys,Object),Yn=Math.max,Zn=Ce(On,"DataView"),er=Ce(On,"Map"),tr=Ce(On,"Promise"),nr=Ce(On,"Set"),rr=Ce(On,"WeakMap"),or=Ce(Object,"create"),ar=!Xn.call({valueOf:1},"valueOf"),ir=nt(Zn),ur=nt(er),sr=nt(tr),lr=nt(nr),cr=nt(rr),fr=zn?zn.prototype:Tt,pr=fr?fr.valueOf:Tt,yr=fr?fr.toString:Tt;m.prototype.clear=_,m.prototype.delete=w,m.prototype.get=A,m.prototype.has=j,m.prototype.set=k,O.prototype.clear=E,O.prototype.delete=x,O.prototype.get=M,O.prototype.has=T,O.prototype.set=N,P.prototype.clear=B,P.prototype.delete=F,P.prototype.get=S,P.prototype.has=L,P.prototype.set=I,D.prototype.add=D.prototype.push=R,D.prototype.has=U,$.prototype.clear=C,$.prototype.delete=G,$.prototype.get=z,$.prototype.has=W,$.prototype.set=q;var dr=Be(Z),gr=Fe(),vr=Jn?v(Jn,Object):xt,hr=ne;(Zn&&hr(new Zn(new ArrayBuffer(1)))!=tn||er&&hr(new er)!=qt||tr&&hr(tr.resolve())!=Ht||nr&&hr(new nr)!=Kt||rr&&hr(new rr)!=Zt)&&(hr=function(e){var t=$n.call(e),n=t==Xt?e.constructor:Tt,r=n?nt(n):Tt;if(r)switch(r){case ir:return tn;case ur:return qt;case sr:return Ht; -case lr:return Kt;case cr:return Zt}return t});var br=at(function(e){e=mt(e);var t=[];return dn.test(e)&&t.push(""),e.replace(gn,function(e,n,r,o){t.push(r?o.replace(hn,"$1"):n||e)}),t});at.Cache=P;var mr=Array.isArray,_r=Kn||Mt,wr=Pn?p(Pn):se,Ar=Pe(function(e,t,n){de(e,t,n)});b.compact=rt,b.iteratee=Ot,b.keys=At,b.keysIn=jt,b.memoize=at,b.merge=Ar,b.property=Et,b.toPlainObject=bt,b.clone=it,b.eq=ut,b.forEach=ot,b.get=_t,b.hasIn=wt,b.identity=kt,b.isArguments=st,b.isArray=mr,b.isArrayLike=lt,b.isArrayLikeObject=ct,b.isBuffer=_r,b.isEmpty=ft,b.isFunction=pt,b.isLength=yt,b.isObject=dt,b.isObjectLike=gt,b.isPlainObject=vt,b.isSymbol=ht,b.isTypedArray=wr,b.stubArray=xt,b.stubFalse=Mt,b.toString=mt,b.each=ot,b.VERSION=Nt,xn&&((xn.exports=b)._=b,En._=b)}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],3:[function(e,t,n){function r(e,t){switch(e&&e.type||null){case"FeatureCollection":return e.features=e.features.map(o(r,t)),e;case"Feature":return e.geometry=r(e.geometry,t),e;case"Polygon":case"MultiPolygon":return a(e,t);default:return e}}function o(e,t){return function(n){return e(n,t)}}function a(e,t){return"Polygon"===e.type?e.coordinates=i(e.coordinates,t):"MultiPolygon"===e.type&&(e.coordinates=e.coordinates.map(o(i,t))),e}function i(e,t){t=!!t,e[0]=u(e[0],!t);for(var n=1;n=0}var l=e("geojson-area");t.exports=r},{"geojson-area":4}],4:[function(e,t,n){function r(e){if("Polygon"===e.type)return o(e.coordinates);if("MultiPolygon"===e.type){for(var t=0,n=0;n0){t+=Math.abs(a(e[0]));for(var n=1;n2){for(var n,r,o=0;o-1},qe.prototype.set=function(e,t){var n=this.__data__,r=et(n,e);return r<0?n.push([e,t]):n[r][1]=t,this},He.prototype.clear=function(){this.__data__={hash:new Xe,map:new(Pe||qe),string:new Xe}},He.prototype.delete=function(e){return ht(this,e).delete(e)},He.prototype.get=function(e){return ht(this,e).get(e)},He.prototype.has=function(e){return ht(this,e).has(e)},He.prototype.set=function(e,t){return ht(this,e).set(e,t),this},Je.prototype.add=Je.prototype.push=function(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this},Je.prototype.has=function(e){return this.__data__.has(e)},Ke.prototype.clear=function(){this.__data__=new qe},Ke.prototype.delete=function(e){return this.__data__.delete(e)},Ke.prototype.get=function(e){return this.__data__.get(e)},Ke.prototype.has=function(e){return this.__data__.has(e)},Ke.prototype.set=function(e,t){var n=this.__data__;if(n instanceof qe){var r=n.__data__;if(!Pe||r.length<199)return r.push([e,t]),this;n=this.__data__=new He(r)}return n.set(e,t),this};var nt,rt=(nt=function(e,t){return e&&ot(e,t,Jt)},function(e,t){if(null==e)return e;if(!Ft(e))return nt(e,t);for(var n=e.length,r=-1,o=Object(e);++rc))return!1;var p=s.get(e);if(p&&s.get(t))return p==t;var y=-1,d=!0,g=u&o?new Je:n;for(s.set(e,t),s.set(t,e);++y-1&&e%1==0&&e-1&&e%1==0&&e<=u}function Dt(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function Ut(e){return!!e&&"object"==typeof e}function Gt(e){if(!Ut(e)||he.call(e)!=h||oe(e))return!1;var t=Ae(e);if(null===t)return!0;var n=ge.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&de.call(n)==ve}function zt(e){return"symbol"==typeof e||Ut(e)&&he.call(e)==w}var Ct,Wt=Y?(Ct=Y,function(e){return Ct(e)}):function(e){return Ut(e)&&$t(e.length)&&!!z[he.call(e)]};function Vt(e){return dt(e,Kt(e))}function Xt(e){return null==e?"":function(e){if("string"==typeof e)return e;if(zt(e))return We?We.call(e):"";var t=e+"";return"0"==t&&1/e==-a?"-0":t}(e)}function qt(e,t,r){var o=null==e?n:it(e,t);return o===n?r:o}function Ht(e,t){return null!=e&&function(e,t,n){for(var r,o=-1,i=(t=At(t,e)?[t]:ft(t)).length;++o1?r[i-1]:n,u=i>2?r[2]:n;for(a=e.length>3&&"function"==typeof a?(i--,a):n,u&&function(e,t,n){if(!Dt(n))return!1;var r=typeof t;return!!("number"==r?Ft(n)&&wt(t,n.length):"string"==r&&t in n)&&Pt(n[t],e)}(r[0],r[1],u)&&(a=i<3?n:a,i=1),t=Object(t);++o2){for(s=0;s=0}(e)===t?e:e.reverse()}n.ring=r;var s,l=function e(t,n){switch(t&&t.type||null){case"FeatureCollection":return t.features=t.features.map(i(e,n)),t;case"Feature":return t.geometry=e(t.geometry,n),t;case"Polygon":case"MultiPolygon":return function(e,t){return"Polygon"===e.type?e.coordinates=a(e.coordinates,t):"MultiPolygon"===e.type&&(e.coordinates=e.coordinates.map(i(a,t))),e}(t,n);default:return t}},c={};function f(t,n){return(t.version||n.version)&&t.version!==n.version?(+t.version||0)>(+n.version||0)?t:n:e.merge(t,n)}function p(e){for(var t,n,r,o,i,a,u=function(e){return e[0]},s=function(e){return e[e.length-1]},l=function(e,t){return void 0!==e&&void 0!==t&&e.id===t.id},c=[];e.length;)for(t=e.pop().nodes.slice(),c.push(t);e.length&&!l(u(t),s(t));){for(n=u(t),r=s(t),o=0;o0&&(y.nodes=f),e.isEmpty(u)||(y.tags=u),(n=t.getElementsByTagName("center")[0])&&l(y,n),p?function(t,n){e.isArray(t.nodes)||(t.nodes=[],e.each(n,function(e,n){t.nodes.push("_anonymous@"+e.getAttribute("lat")+"/"+e.getAttribute("lon"))})),e.each(n,function(e,n){var r,o,a;e.getAttribute("lat")&&(r=e.getAttribute("lat"),o=e.getAttribute("lon"),a={type:"node",id:t.nodes[n],lat:r,lon:o},i.push(a))})}(y,t.getElementsByTagName("nd")):(r=t.getElementsByTagName("bounds")[0])&&c(y,r),a.push(y)}),e.each(t.getElementsByTagName("relation"),function(t,o){var f={},p=[];e.each(t.getElementsByTagName("tag"),function(e){f[e.getAttribute("k")]=e.getAttribute("v")});var y=!1;e.each(t.getElementsByTagName("member"),function(e,t){p[t]={},s(e,p[t],"ref"),s(e,p[t],"role"),s(e,p[t],"type"),(!y&&"node"==p[t].type&&e.getAttribute("lat")||"way"==p[t].type&&e.getElementsByTagName("nd").length>0)&&(y=!0)});var d={type:"relation"};s(t,d,"id"),s(t,d,"version"),s(t,d,"timestamp"),s(t,d,"changeset"),s(t,d,"uid"),s(t,d,"user"),p.length>0&&(d.members=p),e.isEmpty(f)||(d.tags=f),(n=t.getElementsByTagName("center")[0])&&l(d,n),y?function(t,n){e.each(n,function(n,r){var o,u,s;"node"==t.members[r].type?n.getAttribute("lat")&&(o=n.getAttribute("lat"),u=n.getAttribute("lon"),s={type:"node",id:t.members[r].ref,lat:o,lon:u},i.push(s)):"way"==t.members[r].type&&n.getElementsByTagName("nd").length>0&&(t.members[r].ref="_fullGeom"+t.members[r].ref,function(t,n){if(!a.some(function(e){return"way"==e.type&&e.id==n})){var r={type:"way",id:n,nodes:[]};e.each(t,function(e){var t,n,o;e.getAttribute("lat")?(o={type:"node",id:"_anonymous@"+(t=e.getAttribute("lat"))+"/"+(n=e.getAttribute("lon")),lat:t,lon:n},r.nodes.push(o.id),i.push(o)):r.nodes.push(void 0)}),a.push(r)}}(n.getElementsByTagName("nd"),t.members[r].ref))})}(d,t.getElementsByTagName("member")):(r=t.getElementsByTagName("bounds")[0])&&c(d,r),u.push(d)}),o(i,a,u)}(t):function(t){var n=new Array,r=new Array,i=new Array;function a(t){var r=e.clone(t);r.lat=t.center.lat,r.lon=t.center.lon,r.__is_center_placeholder=!0,n.push(r)}function u(t){var o=e.clone(t);function i(e,t,r){var i={type:"node",id:"_"+o.type+"/"+o.id+"bounds"+r,lat:e,lon:t};o.nodes.push(i.id),n.push(i)}o.nodes=[],i(o.bounds.minlat,o.bounds.minlon,1),i(o.bounds.maxlat,o.bounds.minlon,2),i(o.bounds.maxlat,o.bounds.maxlon,3),i(o.bounds.minlat,o.bounds.maxlon,4),o.nodes.push(o.nodes[0]),o.__is_bounds_placeholder=!0,r.push(o)}function s(t){e.isArray(t.nodes)||(t.nodes=t.geometry.map(function(e){return null!==e?"_anonymous@"+e.lat+"/"+e.lon:"_anonymous@unknown_location"})),t.geometry.forEach(function(e,r){var o,i,a;e&&(o=e.lat,i=e.lon,a={type:"node",id:t.nodes[r],lat:o,lon:i},n.push(a))})}function l(e){e.members.forEach(function(e,t){var o,i,a;"node"==e.type?e.lat&&(o=e.lat,i=e.lon,a={type:"node",id:e.ref,lat:o,lon:i},n.push(a)):"way"==e.type&&e.geometry&&(e.ref="_fullGeom"+e.ref,function(e,t){if(!r.some(function(e){return"way"==e.type&&e.id==t})){var o={type:"way",id:t,nodes:[]};e.forEach(function(e){var t,r,i;e?(i={type:"node",id:"_anonymous@"+(t=e.lat)+"/"+(r=e.lon),lat:t,lon:r},o.nodes.push(i.id),n.push(i)):o.nodes.push(void 0)}),r.push(o)}}(e.geometry,e.ref))})}for(var c=0;c0});y.center&&a(y),d?l(y):y.bounds&&u(y)}return o(n,r,i)}(t);function o(t,o,a){function u(e,t){if("object"!=typeof t&&(t={}),"function"==typeof n.uninterestingTags)return!n.uninterestingTags(e,t);for(var r in e)if(!0!==n.uninterestingTags[r]&&!0!==t[r]&&t[r]!==e[r])return!0;return!1}function s(e){var t={timestamp:e.timestamp,version:e.version,changeset:e.changeset,user:e.user,uid:e.uid,ndrefs:e.hasOwnProperty("ndrefs")&&n.wayRefs?e.ndrefs:void 0};for(var r in t)void 0===t[r]&&delete t[r];return t}for(var c=new Object,f=new Object,y=0;yr!=c>r&&n<(l-u)*(r-s)/(c-s)+u&&(o=!o)}return o};for(e=r(e),t=0;t=0.5" diff --git a/test/osm.test.js b/test/osm.test.js index f03fda8..16db05e 100644 --- a/test/osm.test.js +++ b/test/osm.test.js @@ -125,7 +125,148 @@ describe("osm (xml)", function () { expect(osmtogeojson(xml, {flatProperties: false})).to.eql(geojson); }); + it('relation', function() { + var xml, geojson; + + xml = ""; + xml = (new DOMParser()).parseFromString(xml, 'text/xml'); + expectedResult = { + "geojson": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "way/2", + "properties": { + "type": "way", + "id": 2, + "tags": { + "area": "yes" + }, + "relations": [], + "meta": {} + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -1, + -1 + ], + [ + 1, + -1 + ], + [ + 1, + 1 + ], + [ + -1, + 1 + ], + [ + -1, + -1 + ] + ] + ] + } + }, + { + "type": "Feature", + "id": "way/3", + "properties": { + "type": "way", + "id": 3, + "tags": {}, + "relations": [], + "meta": {} + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 0, + -0.5 + ], + [ + 0, + 0.5 + ], + [ + 0.5, + 0 + ], + [ + 0, + -0.5 + ] + ] + } + } + ] + }, + "featuresInRelation": [ + "way/2", + "way/3" + ], + "nodes": [ + { + "type": "node", + "id": "4", + "lat": "-1.0", + "lon": "-1.0", + "tags": {} + }, + { + "type": "node", + "id": "5", + "lat": "-1.0", + "lon": "1.0", + "tags": {} + }, + { + "type": "node", + "id": "6", + "lat": "1.0", + "lon": "1.0", + "tags": {} + }, + { + "type": "node", + "id": "7", + "lat": "1.0", + "lon": "-1.0", + "tags": {} + }, + { + "type": "node", + "id": "8", + "lat": "-0.5", + "lon": "0.0", + "tags": {} + }, + { + "type": "node", + "id": "9", + "lat": "0.5", + "lon": "0.0", + "tags": {} + }, + { + "type": "node", + "id": "10", + "lat": "0.0", + "lon": "0.5", + "tags": {} + } + ] + }; + expect(osmtogeojson(xml, {flatProperties: false, mapRelations: true})).to.eql(expectedResult); + }); }); describe("osm (json)", function () { @@ -2083,6 +2224,67 @@ describe("options", function () { result = osmtogeojson(json); expect(result.features[0].properties).to.eql(geojson_properties); }); + + it("add way node references without flatProperties", function () { + var xml, geojson; + + xml = ""; + xml = (new DOMParser()).parseFromString(xml, 'text/xml'); + geojson = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + id: "way/1", + properties: { + type: "way", + id: 1, + tags: {}, + relations: [], + meta: {ndrefs: [2, 3, 4]} + }, + geometry: { + type: "LineString", + coordinates: [ + [1.0,0.0], + [1.1,0.0], + [1.2,0.1], + ] + } + } + ] + }; + expect(osmtogeojson(xml, {flatProperties: false, wayRefs: true})).to.eql(geojson); + }) + + it("add way node references with flatProperties", function () { + var xml, geojson; + + xml = ""; + xml = (new DOMParser()).parseFromString(xml, 'text/xml'); + geojson = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + id: "way/1", + properties: { + id: "way/1", + ndrefs: [2, 3, 4] + }, + geometry: { + type: "LineString", + coordinates: [ + [1.0,0.0], + [1.1,0.0], + [1.2,0.1], + ] + } + } + ] + }; + expect(osmtogeojson(xml, {flatProperties: true, wayRefs: true})).to.eql(geojson); + }) // interesting objects it("uninteresting tags", function () { var json;