From 631ef53aa17f49a83ae89a72f9b7bd208adbd2e6 Mon Sep 17 00:00:00 2001 From: hparkertt Date: Fri, 6 Sep 2019 15:45:19 -0400 Subject: [PATCH 1/6] Adding undefined/null check for `container.lat` scenarios. --- geojson.js | 4 ++-- geojson.min.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/geojson.js b/geojson.js index 8550a87..df807a4 100644 --- a/geojson.js +++ b/geojson.js @@ -230,7 +230,7 @@ var paths = val[i].split('.'); var itemClone = item; for (var j = 0; j < paths.length; j++) { - if (!itemClone.hasOwnProperty(paths[j])) { + if (itemClone == undefined || !itemClone.hasOwnProperty(paths[j])) { return false; } itemClone = itemClone[paths[j]]; // Iterate deeper into the object @@ -248,7 +248,7 @@ var paths = val[i].split('.'); var itemClone = item; for (var j = 0; j < paths.length; j++) { - if (!itemClone.hasOwnProperty(paths[j])) { + if (itemClone == undefined || !itemClone.hasOwnProperty(paths[j])) { return false; } itemClone = itemClone[paths[j]]; // Iterate deeper into the object diff --git a/geojson.min.js b/geojson.min.js index e727334..1a4548b 100644 --- a/geojson.min.js +++ b/geojson.min.js @@ -1,3 +1,4 @@ // geojson.js - v0.4.1 -// (c) 2016 Casey Thomas, MIT License -!function(a){function b(){var a=1<=arguments.length?[].slice.call(arguments,0):[],b=a.shift(),c=a.shift();Error.apply(this,a),this.message=this.message||"Invalid Geometry: item: "+JSON.stringify(b)+", params: "+JSON.stringify(c)}function c(a,b){var c=a||{};for(var d in b)b.hasOwnProperty(d)&&!c[d]&&(c[d]=b[d]);return c}function d(a,b){if(b.crs&&e(b.crs)&&(b.isPostgres?a.geometry.crs=b.crs:a.crs=b.crs),b.bbox&&(a.bbox=b.bbox),b.extraGlobal){a.properties={};for(var c in b.extraGlobal)a.properties[c]=b.extraGlobal[c]}}function e(a){if("name"===a.type){if(a.properties&&a.properties.name)return!0;throw new Error('Invalid CRS. Properties must contain "name" key')}if("link"===a.type){if(a.properties&&a.properties.href&&a.properties.type)return!0;throw new Error('Invalid CRS. Properties must contain "href" and "type" key')}throw new Error('Invald CRS. Type attribute must be "name" or "link"')}function f(a){a.geom={};for(var b in a)a.hasOwnProperty(b)&&-1!==m.indexOf(b)&&(a.geom[b]=a[b],delete a[b]);g(a.geom)}function g(a){for(var b in a)a.hasOwnProperty(b)&&("string"==typeof a[b]?n.push(a[b]):"object"==typeof a[b]&&(n.push(a[b][0]),n.push(a[b][1])));if(0===n.length)throw new Error("No geometry attributes specified")}function h(a){var b=a.item,c=a.params,d=a.propFunc,e={type:"Feature"};return e.geometry=j(b,c),e.properties=d.call(b),e}function i(a){return/^.+\..+$/.test(a)}function j(c,d){var e={};for(var f in d.geom){var g=d.geom[f];if("string"==typeof g&&c.hasOwnProperty(g))"GeoJSON"===f?e=c[g]:(e.type=f,e.coordinates=c[g]);else if("object"!=typeof g||Array.isArray(g))if(Array.isArray(g)&&c.hasOwnProperty(g[0])&&c.hasOwnProperty(g[1]))e.type=f,e.coordinates=[Number(c[g[1]]),Number(c[g[0]])];else if(Array.isArray(g)&&i(g[0])&&i(g[1])){for(var h=[],k=0;k Date: Mon, 30 Sep 2019 16:51:09 -0400 Subject: [PATCH 2/6] Adjusting variable names and definitions to resolve jshint task warnings. --- geojson.js | 23 ++++++++++++----------- geojson.min.js | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/geojson.js b/geojson.js index df807a4..6bf8391 100644 --- a/geojson.js +++ b/geojson.js @@ -180,6 +180,9 @@ for(var gtype in params.geom) { var val = params.geom[gtype]; + var coordinates = []; + var itemClone; + var paths; // Geometry parameter specified as: {Point: 'coords'} if(typeof val === 'string' && item.hasOwnProperty(val)) { @@ -225,10 +228,9 @@ // Geometry parameter specified as: {Point: ['container.lat', 'container.lng', 'container.alt']} else if(Array.isArray(val) && isNested(val[0]) && isNested(val[1]) && isNested(val[2])){ - var coordinates = []; for (var i = 0; i < val.length; i++) { // i.e. 0 and 1 - var paths = val[i].split('.'); - var itemClone = item; + paths = val[i].split('.'); + itemClone = item; for (var j = 0; j < paths.length; j++) { if (itemClone == undefined || !itemClone.hasOwnProperty(paths[j])) { return false; @@ -243,17 +245,16 @@ // Geometry parameter specified as: {Point: ['container.lat', 'container.lng']} else if(Array.isArray(val) && isNested(val[0]) && isNested(val[1])){ - var coordinates = []; - for (var i = 0; i < val.length; i++) { // i.e. 0 and 1 - var paths = val[i].split('.'); - var itemClone = item; - for (var j = 0; j < paths.length; j++) { - if (itemClone == undefined || !itemClone.hasOwnProperty(paths[j])) { + for (var k = 0; k < val.length; k++) { // i.e. 0 and 1 + paths = val[k].split('.'); + itemClone = item; + for (var l = 0; l < paths.length; l++) { + if (itemClone == undefined || !itemClone.hasOwnProperty(paths[l])) { return false; } - itemClone = itemClone[paths[j]]; // Iterate deeper into the object + itemClone = itemClone[paths[l]]; // Iterate deeper into the object } - coordinates[i] = itemClone; + coordinates[k] = itemClone; } geom.type = gtype; geom.coordinates = [Number(coordinates[1]), Number(coordinates[0])]; diff --git a/geojson.min.js b/geojson.min.js index 1a4548b..c000d1b 100644 --- a/geojson.min.js +++ b/geojson.min.js @@ -1,4 +1,4 @@ // geojson.js - v0.4.1 // (c) 2019 Casey Cesari, MIT License -!function(y){function l(){var r=1<=arguments.length?[].slice.call(arguments,0):[],e=r.shift(),t=r.shift();Error.apply(this,r),this.message=this.message||"Invalid Geometry: item: "+JSON.stringify(e)+", params: "+JSON.stringify(t)}y.version="0.4.1",y.defaults={doThrows:{invalidGeometry:!1}},l.prototype=Error,y.errors={InvalidGeometryError:l},y.isGeometryValid=function(r){return!(!r||!Object.keys(r).length)&&(!!r.type&&!!r.coordinates&&Array.isArray(r.coordinates)&&!!r.coordinates.length)},y.parse=function(r,e,t){var o,n,i=function(r,e){var t=r||{};for(var o in e)e.hasOwnProperty(o)&&!t[o]&&(t[o]=e[o]);return t}(e,this.defaults);if(s.length=0,function(r){for(var e in r.geom={},r)r.hasOwnProperty(e)&&-1!==a.indexOf(e)&&(r.geom[e]=r[e],delete r[e]);!function(r){for(var e in r)r.hasOwnProperty(e)&&("string"==typeof r[e]?s.push(r[e]):"object"==typeof r[e]&&(s.push(r[e][0]),s.push(r[e][1])));if(0===s.length)throw new Error("No geometry attributes specified")}(r.geom)}(i),n=function(t){var e;t.exclude||t.include?t.include?e=function(e){t.include.forEach(function(r){e[r]=this[r]},this)}:t.exclude&&(e=function(r){for(var e in this)this.hasOwnProperty(e)&&-1===s.indexOf(e)&&-1===t.exclude.indexOf(e)&&(r[e]=this[e])}):e=function(r){for(var e in this)this.hasOwnProperty(e)&&-1===s.indexOf(e)&&(r[e]=this[e])};return function(){var r={};return e.call(this,r),t.extra&&function(r,e){for(var t in e)e.hasOwnProperty(t)&&(r[t]=e[t])}(r,t.extra),r}}(i),Array.isArray(r)?(o={type:"FeatureCollection",features:[]},r.forEach(function(r){o.features.push(p({item:r,params:i,propFunc:n}))}),u(o,i)):u(o=p({item:r,params:i,propFunc:n}),i),!t||"function"!=typeof t)return o;t(o)};var a=["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeoJSON"],s=[];function u(r,e){if(e.crs&&function(r){{if("name"===r.type){if(r.properties&&r.properties.name)return!0;throw new Error('Invalid CRS. Properties must contain "name" key')}if("link"!==r.type)throw new Error('Invald CRS. Type attribute must be "name" or "link"');if(r.properties&&r.properties.href&&r.properties.type)return!0;throw new Error('Invalid CRS. Properties must contain "href" and "type" key')}}(e.crs)&&(e.isPostgres?r.geometry.crs=e.crs:r.crs=e.crs),e.bbox&&(r.bbox=e.bbox),e.extraGlobal)for(var t in r.properties={},e.extraGlobal)r.properties[t]=e.extraGlobal[t]}function p(r){var e=r.item,t=r.params,o=r.propFunc,n={type:"Feature"};return n.geometry=function o(n,r){var e={};for(var t in r.geom){var i=r.geom[t];if("string"==typeof i&&n.hasOwnProperty(i))"GeoJSON"===t?e=n[i]:(e.type=t,e.coordinates=n[i]);else if("object"!=typeof i||Array.isArray(i))if(Array.isArray(i)&&n.hasOwnProperty(i[0])&&n.hasOwnProperty(i[1])&&n.hasOwnProperty(i[2]))e.type=t,e.coordinates=[Number(n[i[1]]),Number(n[i[0]]),Number(n[i[2]])];else if(Array.isArray(i)&&n.hasOwnProperty(i[0])&&n.hasOwnProperty(i[1]))e.type=t,e.coordinates=[Number(n[i[1]]),Number(n[i[0]])];else if(Array.isArray(i)&&h(i[0])&&h(i[1])&&h(i[2])){for(var a=[],s=0;s Date: Tue, 1 Oct 2019 12:39:08 -0400 Subject: [PATCH 3/6] Adding tests for nested point arguments and undefined geometry values. --- test/test.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/test.js b/test/test.js index d321452..9ea2ba9 100644 --- a/test/test.js +++ b/test/test.js @@ -481,6 +481,57 @@ describe('GeoJSON', function() { }); }); + it('can accept nested arguments for Point', function(done) { + var data = [ + { name: 'Location A', category: 'Store', location: { point: { lat: 39.984, lng: -75.343 } } } + ]; + + GeoJSON.parse(data, { Point: ['location.point.lat', 'location.point.lng'] }, function(geojson) { + expect(geojson.type).to.be('FeatureCollection'); + expect(geojson.features).to.be.an('array'); + expect(geojson.features.length).to.be(1); + expect(geojson.features[0].geometry.coordinates[0]).to.equal(-75.343); + expect(geojson.features[0].geometry.coordinates[1]).to.equal(39.984); + done(); + }); + }); + + it('can handle null or undefined values when parsing nested arguments', function(done) { + var data = [ + { geo: null }, + { geo: { line: [[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0] ] } }, + { geo: { polygon: [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] } }, + { geo: { multipoint: [ [100.0, 0.0], [101.0, 1.0] ] } }, + { geo: { multipolygon: [ + [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], + [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], + [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]] + ]}}, + { geo: { multilinestring: [ [ [100.0, 0.0], [101.0, 1.0] ], [ [102.0, 2.0], [103.0, 3.0] ] ] } } + ]; + + GeoJSON.parse(data, { + Point: ['geo.nope.point.lng', 'geo.nope.point.lat'], + LineString: 'geo.nope.line', + Polygon: 'geo.nope.polygon', + MultiPoint: 'geo.nope.multipoint', + MultiPolygon: 'geo.nope.multipolygon', + MultiLineString: 'geo.nope.multilinestring' + }, function(geojson) { + expect(geojson.type).to.be('FeatureCollection'); + expect(geojson.features).to.be.an('array'); + expect(geojson.features.length).to.be(6); + expect(geojson.features[0].geometry).to.be(false); + expect(geojson.features[1].geometry).to.be(false); + expect(geojson.features[2].geometry).to.be(false); + expect(geojson.features[3].geometry).to.be(false); + expect(geojson.features[4].geometry).to.be(false); + expect(geojson.features[5].geometry).to.be(false); + done(); + }); + + }); + it('can accept up to three arguments for Point in a nested structure', function(done) { var data = [ { name: 'Location A', category: 'Store', location: { lat: 39.984, lng: -75.343, alt: 22026.46 }, street: 'Market' }, From c180b0bbb65e2a00028932982d63a945eeabcca3 Mon Sep 17 00:00:00 2001 From: hparkertt Date: Tue, 1 Oct 2019 17:17:41 -0400 Subject: [PATCH 4/6] Added support for nested properties for non-Point geometries. --- geojson.js | 94 ++++++++++++++++++++++++++++++++++++++------------ geojson.min.js | 2 +- test/test.js | 65 ++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 23 deletions(-) diff --git a/geojson.js b/geojson.js index 6bf8391..8ae4a30 100644 --- a/geojson.js +++ b/geojson.js @@ -175,7 +175,7 @@ // Assembles the `geometry` property // for the feature output function buildGeom(item, params) { - var geom = {}, + var geom, attr; for(var gtype in params.geom) { @@ -183,14 +183,41 @@ var coordinates = []; var itemClone; var paths; + // If we've already found a matching geometry, stop the loop. + if (geom !== undefined && geom !== false) { + break; + } // Geometry parameter specified as: {Point: 'coords'} if(typeof val === 'string' && item.hasOwnProperty(val)) { if(gtype === 'GeoJSON') { geom = item[val]; } else { - geom.type = gtype; - geom.coordinates = item[val]; + geom = { + type: gtype, + coordinates: item[val] + }; + } + } + + // Geometry parameter specified as: {Point: 'geo.coords'} + else if(typeof val === 'string' && isNested(val)) { + geom = undefined; + paths = val.split('.'); + itemClone = item; + for (var m = 0; m < paths.length; m++) { + if (itemClone == undefined || !itemClone.hasOwnProperty(paths[m])) { + m = paths.length; + geom = false; + } else { + itemClone = itemClone[paths[m]]; // Iterate deeper into the object + } + } + if (geom !== false) { + geom = { + type: gtype, + coordinates: itemClone + }; } } @@ -207,40 +234,54 @@ var newItem = item[key]; return buildGeom(newItem, {geom:{ Point: order}}); }); - geom.type = gtype; - /*jshint loopfunc: true */ - geom.coordinates = [].concat(points.map(function(p){ - return p.coordinates; - })); + geom = { + type: gtype, + /*jshint loopfunc: true */ + coordinates: [].concat(points.map(function(p){ + return p.coordinates; + })) + }; } // Geometry parameter specified as: {Point: ['lat', 'lng', 'alt']} else if(Array.isArray(val) && item.hasOwnProperty(val[0]) && item.hasOwnProperty(val[1]) && item.hasOwnProperty(val[2])){ - geom.type = gtype; - geom.coordinates = [Number(item[val[1]]), Number(item[val[0]]), Number(item[val[2]])]; + geom = { + type: gtype, + coordinates: [ Number(item[val[1]]), Number(item[val[0]]), Number(item[val[2]]) ] + }; } // Geometry parameter specified as: {Point: ['lat', 'lng']} else if(Array.isArray(val) && item.hasOwnProperty(val[0]) && item.hasOwnProperty(val[1])){ - geom.type = gtype; - geom.coordinates = [Number(item[val[1]]), Number(item[val[0]])]; + geom = { + type: gtype, + coordinates: [Number(item[val[1]]), Number(item[val[0]])] + }; } // Geometry parameter specified as: {Point: ['container.lat', 'container.lng', 'container.alt']} else if(Array.isArray(val) && isNested(val[0]) && isNested(val[1]) && isNested(val[2])){ + geom = undefined; for (var i = 0; i < val.length; i++) { // i.e. 0 and 1 paths = val[i].split('.'); itemClone = item; for (var j = 0; j < paths.length; j++) { if (itemClone == undefined || !itemClone.hasOwnProperty(paths[j])) { - return false; + i = val.length; + j = paths.length; + geom = false; + } else { + itemClone = itemClone[paths[j]]; // Iterate deeper into the object } - itemClone = itemClone[paths[j]]; // Iterate deeper into the object } coordinates[i] = itemClone; } - geom.type = gtype; - geom.coordinates = [Number(coordinates[1]), Number(coordinates[0]), Number(coordinates[2])]; + if (geom !== false) { + geom = { + type: gtype, + coordinates: [Number(coordinates[1]), Number(coordinates[0]), Number(coordinates[2])] + }; + } } // Geometry parameter specified as: {Point: ['container.lat', 'container.lng']} @@ -250,20 +291,29 @@ itemClone = item; for (var l = 0; l < paths.length; l++) { if (itemClone == undefined || !itemClone.hasOwnProperty(paths[l])) { - return false; + k = val.length; + l = paths.length; + geom = false; + } else { + itemClone = itemClone[paths[l]]; // Iterate deeper into the object } - itemClone = itemClone[paths[l]]; // Iterate deeper into the object } coordinates[k] = itemClone; } - geom.type = gtype; - geom.coordinates = [Number(coordinates[1]), Number(coordinates[0])]; + if (geom !== false) { + geom = { + type: gtype, + coordinates: [Number(coordinates[1]), Number(coordinates[0])] + }; + } } // Geometry parameter specified as: {Point: [{coordinates: [lat, lng]}]} else if (Array.isArray(val) && val[0].constructor.name === 'Object' && Object.keys(val[0])[0] === 'coordinates'){ - geom.type = gtype; - geom.coordinates = [Number(item.coordinates[(val[0].coordinates).indexOf('lng')]), Number(item.coordinates[(val[0].coordinates).indexOf('lat')])]; + geom = { + type: gtype, + coordinates: [Number(item.coordinates[(val[0].coordinates).indexOf('lng')]), Number(item.coordinates[(val[0].coordinates).indexOf('lat')])] + }; } } diff --git a/geojson.min.js b/geojson.min.js index c000d1b..de5699d 100644 --- a/geojson.min.js +++ b/geojson.min.js @@ -1,4 +1,4 @@ // geojson.js - v0.4.1 // (c) 2019 Casey Cesari, MIT License -!function(h){function m(){var r=1<=arguments.length?[].slice.call(arguments,0):[],e=r.shift(),t=r.shift();Error.apply(this,r),this.message=this.message||"Invalid Geometry: item: "+JSON.stringify(e)+", params: "+JSON.stringify(t)}h.version="0.4.1",h.defaults={doThrows:{invalidGeometry:!1}},m.prototype=Error,h.errors={InvalidGeometryError:m},h.isGeometryValid=function(r){return!(!r||!Object.keys(r).length)&&(!!r.type&&!!r.coordinates&&Array.isArray(r.coordinates)&&!!r.coordinates.length)},h.parse=function(r,e,t){var o,n,i=function(r,e){var t=r||{};for(var o in e)e.hasOwnProperty(o)&&!t[o]&&(t[o]=e[o]);return t}(e,this.defaults);if(s.length=0,function(r){for(var e in r.geom={},r)r.hasOwnProperty(e)&&-1!==a.indexOf(e)&&(r.geom[e]=r[e],delete r[e]);!function(r){for(var e in r)r.hasOwnProperty(e)&&("string"==typeof r[e]?s.push(r[e]):"object"==typeof r[e]&&(s.push(r[e][0]),s.push(r[e][1])));if(0===s.length)throw new Error("No geometry attributes specified")}(r.geom)}(i),n=function(t){var e;t.exclude||t.include?t.include?e=function(e){t.include.forEach(function(r){e[r]=this[r]},this)}:t.exclude&&(e=function(r){for(var e in this)this.hasOwnProperty(e)&&-1===s.indexOf(e)&&-1===t.exclude.indexOf(e)&&(r[e]=this[e])}):e=function(r){for(var e in this)this.hasOwnProperty(e)&&-1===s.indexOf(e)&&(r[e]=this[e])};return function(){var r={};return e.call(this,r),t.extra&&function(r,e){for(var t in e)e.hasOwnProperty(t)&&(r[t]=e[t])}(r,t.extra),r}}(i),Array.isArray(r)?(o={type:"FeatureCollection",features:[]},r.forEach(function(r){o.features.push(p({item:r,params:i,propFunc:n}))}),u(o,i)):u(o=p({item:r,params:i,propFunc:n}),i),!t||"function"!=typeof t)return o;t(o)};var a=["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeoJSON"],s=[];function u(r,e){if(e.crs&&function(r){{if("name"===r.type){if(r.properties&&r.properties.name)return!0;throw new Error('Invalid CRS. Properties must contain "name" key')}if("link"!==r.type)throw new Error('Invald CRS. Type attribute must be "name" or "link"');if(r.properties&&r.properties.href&&r.properties.type)return!0;throw new Error('Invalid CRS. Properties must contain "href" and "type" key')}}(e.crs)&&(e.isPostgres?r.geometry.crs=e.crs:r.crs=e.crs),e.bbox&&(r.bbox=e.bbox),e.extraGlobal)for(var t in r.properties={},e.extraGlobal)r.properties[t]=e.extraGlobal[t]}function p(r){var e=r.item,t=r.params,o=r.propFunc,n={type:"Feature"};return n.geometry=function o(n,r){var e={};for(var t in r.geom){var i,a,s=r.geom[t],u=[];if("string"==typeof s&&n.hasOwnProperty(s))"GeoJSON"===t?e=n[s]:(e.type=t,e.coordinates=n[s]);else if("object"!=typeof s||Array.isArray(s))if(Array.isArray(s)&&n.hasOwnProperty(s[0])&&n.hasOwnProperty(s[1])&&n.hasOwnProperty(s[2]))e.type=t,e.coordinates=[Number(n[s[1]]),Number(n[s[0]]),Number(n[s[2]])];else if(Array.isArray(s)&&n.hasOwnProperty(s[0])&&n.hasOwnProperty(s[1]))e.type=t,e.coordinates=[Number(n[s[1]]),Number(n[s[0]])];else if(Array.isArray(s)&&d(s[0])&&d(s[1])&&d(s[2])){for(var p=0;p Date: Wed, 2 Oct 2019 14:52:23 -0400 Subject: [PATCH 5/6] Added removeInvalidGeometries option. This will only return geometries if they return true from the GeoJSON.isGeometryValid function. --- README.md | 11 +++++++++++ geojson.js | 8 ++++++-- geojson.min.js | 2 +- test/test.js | 14 +++++++++++++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e4f17d..9de4a3c 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,17 @@ You can also add dataset properties using the `extraGlobal` param. The value for } } +#### removeInvalidGeometries + +If passing in a list of data, you can optionally exclude invalid geometries by setting the `removeInvalidGeometries` flag to `true`. This utilizes the `GeoJSON.isGeometryValid` function. + +```javascript +GeoJSON.parse(data, { + Point: ['lat', 'lng'], + removeInvalidGeometries: true +}); +``` + ## Tests For node, `$ npm test`. diff --git a/geojson.js b/geojson.js index 8ae4a30..ebdf21b 100644 --- a/geojson.js +++ b/geojson.js @@ -5,7 +5,8 @@ GeoJSON.defaults = { doThrows: { invalidGeometry: false - } + }, + removeInvalidGeometries: false }; function InvalidGeometryError() { @@ -46,7 +47,10 @@ if (Array.isArray(objects)) { geojson = {"type": "FeatureCollection", "features": []}; objects.forEach(function(item){ - geojson.features.push(getFeature({item:item, params: settings, propFunc:propFunc})); + var feature = getFeature({item:item, params: settings, propFunc:propFunc}); + if (settings.removeInvalidGeometries !== true || GeoJSON.isGeometryValid(feature.geometry)) { + geojson.features.push(feature); + } }); addOptionals(geojson, settings); } else { diff --git a/geojson.min.js b/geojson.min.js index de5699d..a8d3469 100644 --- a/geojson.min.js +++ b/geojson.min.js @@ -1,4 +1,4 @@ // geojson.js - v0.4.1 // (c) 2019 Casey Cesari, MIT License -!function(d){function m(){var r=1<=arguments.length?[].slice.call(arguments,0):[],e=r.shift(),t=r.shift();Error.apply(this,r),this.message=this.message||"Invalid Geometry: item: "+JSON.stringify(e)+", params: "+JSON.stringify(t)}d.version="0.4.1",d.defaults={doThrows:{invalidGeometry:!1}},m.prototype=Error,d.errors={InvalidGeometryError:m},d.isGeometryValid=function(r){return!(!r||!Object.keys(r).length)&&(!!r.type&&!!r.coordinates&&Array.isArray(r.coordinates)&&!!r.coordinates.length)},d.parse=function(r,e,t){var o,n,i=function(r,e){var t=r||{};for(var o in e)e.hasOwnProperty(o)&&!t[o]&&(t[o]=e[o]);return t}(e,this.defaults);if(s.length=0,function(r){for(var e in r.geom={},r)r.hasOwnProperty(e)&&-1!==a.indexOf(e)&&(r.geom[e]=r[e],delete r[e]);!function(r){for(var e in r)r.hasOwnProperty(e)&&("string"==typeof r[e]?s.push(r[e]):"object"==typeof r[e]&&(s.push(r[e][0]),s.push(r[e][1])));if(0===s.length)throw new Error("No geometry attributes specified")}(r.geom)}(i),n=function(t){var e;t.exclude||t.include?t.include?e=function(e){t.include.forEach(function(r){e[r]=this[r]},this)}:t.exclude&&(e=function(r){for(var e in this)this.hasOwnProperty(e)&&-1===s.indexOf(e)&&-1===t.exclude.indexOf(e)&&(r[e]=this[e])}):e=function(r){for(var e in this)this.hasOwnProperty(e)&&-1===s.indexOf(e)&&(r[e]=this[e])};return function(){var r={};return e.call(this,r),t.extra&&function(r,e){for(var t in e)e.hasOwnProperty(t)&&(r[t]=e[t])}(r,t.extra),r}}(i),Array.isArray(r)?(o={type:"FeatureCollection",features:[]},r.forEach(function(r){o.features.push(u({item:r,params:i,propFunc:n}))}),p(o,i)):p(o=u({item:r,params:i,propFunc:n}),i),!t||"function"!=typeof t)return o;t(o)};var a=["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeoJSON"],s=[];function p(r,e){if(e.crs&&function(r){{if("name"===r.type){if(r.properties&&r.properties.name)return!0;throw new Error('Invalid CRS. Properties must contain "name" key')}if("link"!==r.type)throw new Error('Invald CRS. Type attribute must be "name" or "link"');if(r.properties&&r.properties.href&&r.properties.type)return!0;throw new Error('Invalid CRS. Properties must contain "href" and "type" key')}}(e.crs)&&(e.isPostgres?r.geometry.crs=e.crs:r.crs=e.crs),e.bbox&&(r.bbox=e.bbox),e.extraGlobal)for(var t in r.properties={},e.extraGlobal)r.properties[t]=e.extraGlobal[t]}function u(r){var e=r.item,t=r.params,o=r.propFunc,n={type:"Feature"};return n.geometry=function o(n,r){var e;for(var t in r.geom){var i,a,s=r.geom[t],p=[];if(void 0!==e&&!1!==e)break;if("string"==typeof s&&n.hasOwnProperty(s))e="GeoJSON"===t?n[s]:{type:t,coordinates:n[s]};else if("string"==typeof s&&g(s)){e=void 0,a=s.split("."),i=n;for(var u=0;u Date: Thu, 3 Oct 2019 09:03:13 -0400 Subject: [PATCH 6/6] Added isGeometryValid parameter option. This option will be called instead of GeoJSON.isGeometryValid. A new isValid function was created to handle this logic. --- README.md | 22 ++++++++++++++++++ geojson.js | 12 ++++++++-- geojson.min.js | 2 +- test/test.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 91 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9de4a3c..87b774c 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,28 @@ GeoJSON.parse(data, { }); ``` +#### isGeometryValid + +If you would like to provide your own validation for geometries, you can include your own `isGeometryValid` validation function. This will be used instead of the `GeoJSON.isGeometryValid` function. + +```javascript +GeoJSON.parse(data, { + Point: ['lat', 'lng'], + removeInvalidGeometries: true, + isGeometryValid: customValidationFunction +}); +``` + +It may be easier to provide this function as a default, as mentioned above. For example: + +```javascript +GeoJSON.defaults.isGeometryValid = function(geometry){ + // Validation logic +}; +... +GeoJSON.parse(data, {Point: ['lat', 'lng']}); +``` + ## Tests For node, `$ npm test`. diff --git a/geojson.js b/geojson.js index ebdf21b..e2757d0 100644 --- a/geojson.js +++ b/geojson.js @@ -48,7 +48,7 @@ geojson = {"type": "FeatureCollection", "features": []}; objects.forEach(function(item){ var feature = getFeature({item:item, params: settings, propFunc:propFunc}); - if (settings.removeInvalidGeometries !== true || GeoJSON.isGeometryValid(feature.geometry)) { + if (settings.removeInvalidGeometries !== true || isValid(feature.geometry, settings.isGeometryValid)) { geojson.features.push(feature); } }); @@ -321,7 +321,7 @@ } } - if(params.doThrows && params.doThrows.invalidGeometry && !GeoJSON.isGeometryValid(geom)){ + if(params.doThrows && params.doThrows.invalidGeometry && !isValid(geom, params.isGeometryValid)){ throw new InvalidGeometryError(item, params); } @@ -379,4 +379,12 @@ return properties; } + function isValid(geometry, custom) { + if (typeof custom === 'function') { + return custom(geometry); + } else { + return GeoJSON.isGeometryValid(geometry); + } + } + }(typeof module == 'object' ? module.exports : window.GeoJSON = {})); diff --git a/geojson.min.js b/geojson.min.js index a8d3469..cc9dfb0 100644 --- a/geojson.min.js +++ b/geojson.min.js @@ -1,4 +1,4 @@ // geojson.js - v0.4.1 // (c) 2019 Casey Cesari, MIT License -!function(d){function m(){var r=1<=arguments.length?[].slice.call(arguments,0):[],e=r.shift(),t=r.shift();Error.apply(this,r),this.message=this.message||"Invalid Geometry: item: "+JSON.stringify(e)+", params: "+JSON.stringify(t)}d.version="0.4.1",d.defaults={doThrows:{invalidGeometry:!1},removeInvalidGeometries:!1},m.prototype=Error,d.errors={InvalidGeometryError:m},d.isGeometryValid=function(r){return!(!r||!Object.keys(r).length)&&(!!r.type&&!!r.coordinates&&Array.isArray(r.coordinates)&&!!r.coordinates.length)},d.parse=function(r,e,t){var o,n,i=function(r,e){var t=r||{};for(var o in e)e.hasOwnProperty(o)&&!t[o]&&(t[o]=e[o]);return t}(e,this.defaults);if(s.length=0,function(r){for(var e in r.geom={},r)r.hasOwnProperty(e)&&-1!==a.indexOf(e)&&(r.geom[e]=r[e],delete r[e]);!function(r){for(var e in r)r.hasOwnProperty(e)&&("string"==typeof r[e]?s.push(r[e]):"object"==typeof r[e]&&(s.push(r[e][0]),s.push(r[e][1])));if(0===s.length)throw new Error("No geometry attributes specified")}(r.geom)}(i),n=function(t){var e;t.exclude||t.include?t.include?e=function(e){t.include.forEach(function(r){e[r]=this[r]},this)}:t.exclude&&(e=function(r){for(var e in this)this.hasOwnProperty(e)&&-1===s.indexOf(e)&&-1===t.exclude.indexOf(e)&&(r[e]=this[e])}):e=function(r){for(var e in this)this.hasOwnProperty(e)&&-1===s.indexOf(e)&&(r[e]=this[e])};return function(){var r={};return e.call(this,r),t.extra&&function(r,e){for(var t in e)e.hasOwnProperty(t)&&(r[t]=e[t])}(r,t.extra),r}}(i),Array.isArray(r)?(o={type:"FeatureCollection",features:[]},r.forEach(function(r){var e=u({item:r,params:i,propFunc:n});(!0!==i.removeInvalidGeometries||d.isGeometryValid(e.geometry))&&o.features.push(e)}),p(o,i)):p(o=u({item:r,params:i,propFunc:n}),i),!t||"function"!=typeof t)return o;t(o)};var a=["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeoJSON"],s=[];function p(r,e){if(e.crs&&function(r){{if("name"===r.type){if(r.properties&&r.properties.name)return!0;throw new Error('Invalid CRS. Properties must contain "name" key')}if("link"!==r.type)throw new Error('Invald CRS. Type attribute must be "name" or "link"');if(r.properties&&r.properties.href&&r.properties.type)return!0;throw new Error('Invalid CRS. Properties must contain "href" and "type" key')}}(e.crs)&&(e.isPostgres?r.geometry.crs=e.crs:r.crs=e.crs),e.bbox&&(r.bbox=e.bbox),e.extraGlobal)for(var t in r.properties={},e.extraGlobal)r.properties[t]=e.extraGlobal[t]}function u(r){var e=r.item,t=r.params,o=r.propFunc,n={type:"Feature"};return n.geometry=function o(n,r){var e;for(var t in r.geom){var i,a,s=r.geom[t],p=[];if(void 0!==e&&!1!==e)break;if("string"==typeof s&&n.hasOwnProperty(s))e="GeoJSON"===t?n[s]:{type:t,coordinates:n[s]};else if("string"==typeof s&&v(s)){e=void 0,a=s.split("."),i=n;for(var u=0;u -75.8 && + geometry.coordinates[1] < 39.9; + }; + GeoJSON.parse(data, options, function(geojson) { + expect(geojson.features.length).to.be(1); + done(); + }); + }); + + it('will only return geometries that pass a custom validation check provided as a default.', function(done){ + GeoJSON.defaults = { Point: ['lat', 'lng'], removeInvalidGeometries: true }; + GeoJSON.defaults.isGeometryValid = function(geometry){ + return GeoJSON.isGeometryValid(geometry) && + geometry.coordinates[0] > -75.8 && + geometry.coordinates[1] < 39.9; + }; + GeoJSON.parse(data, {}, function(geojson) { + expect(geojson.features.length).to.be(1); + delete GeoJSON.defaults; + done(); + }); + }); + + it('will throw an error when a geometry does not pass a custom validation check.', function(){ + var options = { Point: ['lat', 'lng'], doThrows: { invalidGeometry: true } }; + options.isGeometryValid = function(geometry){ + return GeoJSON.isGeometryValid(geometry) && + geometry.coordinates[0] > -75.8 && + geometry.coordinates[1] < 39.9; + }; + expect(function(){ + GeoJSON.parse(data, options); + }).to.throwException(GeoJSON.InvalidGeometryError); + }); }); describe('Parse points as coordinate pair', function(){