From b432abe8ca83984eb1ddf0445ec54ebc318e4f65 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Tue, 5 Dec 2023 13:20:48 -0500 Subject: [PATCH 1/3] Add test cases for nodes without coordinates --- test/osm.test.js | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/test/osm.test.js b/test/osm.test.js index 613d5b7..32ef1bb 100644 --- a/test/osm.test.js +++ b/test/osm.test.js @@ -2835,6 +2835,101 @@ describe("tainted data", function () { }); +describe("references without coordinates", function() { + // ignore references to missing node coordinates + it("way with nodes missing coordinates", function() { + var json = { elements: [ + { type: "way", id: 1, nodes: [2, 3, 4] }, + { type: "node", id: 2 }, + { type: "node", id: 3 }, + { type: "node", id: 4 }, + ] }; + var result = osmtogeojson(json, {flatProperties: false}); + expect(result).property("features").empty(); + }); + // ignore references to ways which have no geometry + it("line relation with ways missing nodes", function() { + var json = { + "elements": [ + { "type": "relation", "id": 1, "members": [ + { "type": "way", "ref": 2, "role": "" }, + { "type": "way", "ref": 3, "role": "" } + ], "tags": { "type": "route" } }, + { "type": "way", "id": 2, "nodes": [4, 5] }, + { "type": "way", "id": 3, "nodes": [6, 7] } + ] + }; + var result = osmtogeojson(json, {flatProperties: false}); + expect(result).property("features").empty(); + }); + it("area relation with ways missing nodes", function() { + var json = { + "elements": [ + { "type": "relation", "id": 1, "members": [ + { "type": "way", "ref": 2, "role": "outer" }, + { "type": "way", "ref": 3, "role": "outer" } + ], "tags": { "type": "multipolygon" } }, + { "type": "way", "id": 2, "nodes": [4, 5, 5, 4] }, + { "type": "way", "id": 3, "nodes": [6, 7, 7, 6] }, + ] + }; + var result = osmtogeojson(json, {flatProperties: false}); + expect(result).property("features").empty(); + }); + it("another way", function() { + var json = { + elements: [ + { type: 'way', id: 1, nodes: [ 2, 3, 4 ] }, + { type: 'node', id: 2, lon: 2, lat: 2 }, + { type: 'node', id: 3 }, + { type: 'node', id: 4, lon: 4, lat: 4 } + ] + }; + var result = osmtogeojson(json, {flatProperties: false}); + expect(result.features[0].properties).property("tainted", true); + expect(result.features[0]).property("geometry").eql({ + type: 'LineString', + coordinates: [ [ 2, 2 ], [ 4, 4 ] ], + }); + }); + it("line relation has ways and nodes but no coordinates", function() { + var json = { + "elements": [ + { "type": "relation", "id": 1, "members": [ + { "type": "way", "ref": 2, "role": "" }, + { "type": "way", "ref": 3, "role": "" } + ], "tags": { "type": "route" } }, + { "type": "way", "id": 2, "nodes": [4, 5] }, + { "type": "way", "id": 3, "nodes": [6, 7] }, + { "type": "node", "id": 4 }, + { "type": "node", "id": 5 }, + { "type": "node", "id": 6 }, + { "type": "node", "id": 7 }, + ] + }; + var result = osmtogeojson(json, {flatProperties: false}); + expect(result).property("features").empty(); + }); + it("area relation has ways and nodes but no coordinates", function() { + var json = { + "elements": [ + { "type": "relation", "id": 1, "members": [ + { "type": "way", "ref": 2, "role": "outer" }, + { "type": "way", "ref": 3, "role": "outer" } + ], "tags": { "type": "multipolygon" } }, + { "type": "way", "id": 2, "nodes": [4, 5, 5, 4] }, + { "type": "way", "id": 3, "nodes": [6, 7, 7, 6] }, + { "type": "node", "id": 4 }, + { "type": "node", "id": 5 }, + { "type": "node", "id": 6 }, + { "type": "node", "id": 7 }, + ] + }; + var result = osmtogeojson(json, {flatProperties: false}); + expect(result).property("features").empty(); + }); +}); + describe("other", function () { // it("sideeffects", function () { From 07f3e437856c4ae00dbd2bc82684f2c1c16462d9 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Tue, 5 Dec 2023 13:25:50 -0500 Subject: [PATCH 2/3] Handle nodes without coordinates Treat nodes without coordinates the same as if they are absent, in construct_multipolygon, construct_multilinestring, and when processing ways, to avoid producing coordinates of NaN/null. When a segment of a relation (either a line string, or a polygon ring) consists entirely of invalid nodes, omit this empty array, to avoid producing a coordinates element which is an empty array. Both of these changes will cause such features to be omitted from the output when they have no coordinates, just as they already are for ways which reference nodes that are absent, and relations which reference ways that are absent. --- index.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 0164459..a1f04cc 100644 --- a/index.js +++ b/index.js @@ -641,7 +641,7 @@ osmtogeojson = function( data, options, featureCallback ) { role: m.role, way: way, nodes: way.nodes.filter(function(n) { - if (n !== undefined) + if (n !== undefined && n.lon !== undefined && n.lat !== undefined) return true; is_tainted = true; if (options.verbose) console.warn('Route', rel.type+'/'+rel.id, 'tainted by a way', m.type+'/'+m.ref, 'with a missing node'); @@ -649,7 +649,9 @@ osmtogeojson = function( data, options, featureCallback ) { }) }; }); - members = _.compact(members); + members = _.compact(members).filter(function(m) { + return m.nodes.length; + }); // construct connected linestrings var linestrings; linestrings = join(members); @@ -762,7 +764,7 @@ osmtogeojson = function( data, options, featureCallback ) { role: m.role || "outer", way: way, nodes: way.nodes.filter(function(n) { - if (n !== undefined) + if (n !== undefined && n.lon !== undefined && n.lat !== undefined) return true; is_tainted = true; if (options.verbose) console.warn('Multipolygon', mp_geometry+'/'+mp_id, 'tainted by a way', m.type+'/'+m.ref, 'with a missing node'); @@ -770,7 +772,9 @@ osmtogeojson = function( data, options, featureCallback ) { }) }; }); - members = _.compact(members); + members = _.compact(members).filter(function(m) { + return m.nodes.length; + }); // construct outer and inner rings var outers, inners; outers = join(members.filter(function(m) {return m.role==="outer";})); @@ -903,7 +907,7 @@ osmtogeojson = function( data, options, featureCallback ) { ways[i].hidden = false; var coords = new Array(); for (j=0;j Date: Tue, 5 Dec 2023 13:32:01 -0500 Subject: [PATCH 3/3] run make --- osmtogeojson.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osmtogeojson.js b/osmtogeojson.js index 9cb6e90..c93b8cb 100644 --- a/osmtogeojson.js +++ b/osmtogeojson.js @@ -1 +1 @@ -!function(e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).osmtogeojson=e()}(function(){return function r(o,i,a){function u(t,e){if(!i[t]){if(!o[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(s)return s(t,!0);throw(e=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",e}n=i[t]={exports:{}},o[t][0].call(n.exports,function(e){return u(o[t][1][e]||e)},n,n.exports,r,o,i,a)}return i[t].exports}for(var s="function"==typeof require&&require,e=0;e(+t.version||0)?e:t:F.merge(e,t)}e("osm-polygon-features").forEach(function(e){var t,n;"all"===e.polygon?r[e.key]=!0:(t="whitelist"===e.polygon?"included_values":"excluded_values",n={},e.values.forEach(function(e){n[e]=!0}),r[e.key]={},r[e.key][t]=n)});function P(e){function t(e){return e[e.length-1]}function n(e,t){return void 0!==e&&void 0!==t&&e.id===t.id}for(var r,o,i,a,u,s,l=[];e.length;)for(r=e.pop().nodes.slice(),l.push(r);e.length&&!n(r[0],t(r));){for(o=r[0],i=t(r),a=0;ar!=c>r&&n<(l-u)*(r-s)/(c-s)+u;if(f)o=!o}return o}(t[n],e))return!0;return!1}(t(a[n]),e))return n}t=(t=t.members.filter(function(e){return"way"===e.type})).map(function(t){var e=y[t.ref];if(void 0!==e&&void 0!==e.nodes)return{id:t.ref,role:t.role||"outer",way:e,nodes:e.nodes.filter(function(e){return void 0!==e||(n=!0,N.verbose&&console.warn("Multipolygon",r+"/"+o,"tainted by a way",t.type+"/"+t.ref,"with a missing node"),!1)})};N.verbose&&console.warn("Multipolygon",r+"/"+o,"tainted by a missing or incomplete way",t.type+"/"+t.ref),n=!0});for(var a=P((t=F.compact(t)).filter(function(e){return"outer"===e.role})),u=P(t.filter(function(e){return"inner"===e.role})),s=a.map(function(e){return[e]}),l=0;l=Math.abs(u)?n-s+u:u-s+n,n=s}0<=n+r!=!!t&&e.reverse()}t.exports=function e(t,n){var r,o=t&&t.type;if("FeatureCollection"===o)for(r=0;r(+t.version||0)?e:t:F.merge(e,t)}e("osm-polygon-features").forEach(function(e){var t,n;"all"===e.polygon?r[e.key]=!0:(t="whitelist"===e.polygon?"included_values":"excluded_values",n={},e.values.forEach(function(e){n[e]=!0}),r[e.key]={},r[e.key][t]=n)});function P(e){function t(e){return e[e.length-1]}function n(e,t){return void 0!==e&&void 0!==t&&e.id===t.id}for(var r,o,i,a,u,s,l=[];e.length;)for(r=e.pop().nodes.slice(),l.push(r);e.length&&!n(r[0],t(r));){for(o=r[0],i=t(r),a=0;ar!=c>r&&n<(l-u)*(r-s)/(c-s)+u;if(f)o=!o}return o}(t[n],e))return!0;return!1}(t(a[n]),e))return n}t=(t=t.members.filter(function(e){return"way"===e.type})).map(function(t){var e=y[t.ref];if(void 0!==e&&void 0!==e.nodes)return{id:t.ref,role:t.role||"outer",way:e,nodes:e.nodes.filter(function(e){return void 0!==e&&void 0!==e.lon&&void 0!==e.lat||(n=!0,N.verbose&&console.warn("Multipolygon",r+"/"+o,"tainted by a way",t.type+"/"+t.ref,"with a missing node"),!1)})};N.verbose&&console.warn("Multipolygon",r+"/"+o,"tainted by a missing or incomplete way",t.type+"/"+t.ref),n=!0});for(var a=P((t=F.compact(t).filter(function(e){return e.nodes.length})).filter(function(e){return"outer"===e.role})),u=P(t.filter(function(e){return"inner"===e.role})),s=a.map(function(e){return[e]}),l=0;l=Math.abs(u)?n-s+u:u-s+n,n=s}0<=n+r!=!!t&&e.reverse()}t.exports=function e(t,n){var r,o=t&&t.type;if("FeatureCollection"===o)for(r=0;r