diff --git a/README.md b/README.md index 8e4f17d..87b774c 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,39 @@ 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 +}); +``` + +#### 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 8550a87..e2757d0 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 || isValid(feature.geometry, settings.isGeometryValid)) { + geojson.features.push(feature); + } }); addOptionals(geojson, settings); } else { @@ -175,19 +179,49 @@ // Assembles the `geometry` property // for the feature output function buildGeom(item, params) { - var geom = {}, + var geom, attr; for(var gtype in params.geom) { var val = params.geom[gtype]; + 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 + }; } } @@ -204,69 +238,90 @@ 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])){ - var coordinates = []; + geom = undefined; 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.hasOwnProperty(paths[j])) { - return false; + if (itemClone == undefined || !itemClone.hasOwnProperty(paths[j])) { + 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']} 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.hasOwnProperty(paths[j])) { - return false; + 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])) { + k = val.length; + l = paths.length; + geom = false; + } else { + itemClone = itemClone[paths[l]]; // Iterate deeper into the object } - itemClone = itemClone[paths[j]]; // Iterate deeper into the object } - coordinates[i] = itemClone; + coordinates[k] = itemClone; + } + if (geom !== false) { + geom = { + type: gtype, + coordinates: [Number(coordinates[1]), Number(coordinates[0])] + }; } - geom.type = gtype; - geom.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')])] + }; } } - if(params.doThrows && params.doThrows.invalidGeometry && !GeoJSON.isGeometryValid(geom)){ + if(params.doThrows && params.doThrows.invalidGeometry && !isValid(geom, params.isGeometryValid)){ throw new InvalidGeometryError(item, params); } @@ -324,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 e727334..cc9dfb0 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 -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(){ var data;