geojson-tools
Advanced tools
Comparing version 0.1.7 to 0.2.0-pre
@@ -0,1 +1,8 @@ | ||
0.2.0 / 2015-06-02 | ||
================== | ||
* Add `isGeoJSON()` check | ||
* Add support for all GeoJSON types | ||
* Add unit tests (coverage incomplete) | ||
0.1.7 / 2014-06-13 | ||
@@ -2,0 +9,0 @@ ================== |
{ | ||
"name": "geojson-tools", | ||
"version": "0.1.7", | ||
"version": "0.2.0-pre", | ||
"description": "Tools for working with location data, using the GeoJSON specification", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"test": "mocha" | ||
}, | ||
@@ -26,8 +26,8 @@ "repository": { | ||
"dependencies": { | ||
"underscore": "^1.6.0" | ||
"underscore": "^1.8.3" | ||
}, | ||
"devDependencies": { | ||
"mocha": "1.18.*", | ||
"chai": "~1.9.1" | ||
"mocha": "2.2.*", | ||
"chai": "2.1.*" | ||
} | ||
} |
@@ -29,2 +29,6 @@ # geojson-tools.js | ||
### Validations | ||
* [isGeoJSON](#isGeoJSON) | ||
## Calculations | ||
@@ -61,3 +65,3 @@ | ||
__Argumenta"" | ||
__Arguments__ | ||
@@ -250,7 +254,7 @@ * linestring - a valid GeoJSON `LineString`, or an array of locations, in the format `[lat, lng]`. | ||
* array - an array of locations, in the format `[lat, lng]`. | ||
* type - the type of `GeoJSON` object to return (note that this is not case-sensitive. | ||
* type - the type of `GeoJSON` object to return (note that this is not case-sensitive). | ||
The default type is 'Point', which is returned when pushing an array of a single set of coordinates. | ||
Other types are `LineString` and `Polygon`. | ||
<b>Note:</b> Other `GeoJSON` types will be supported in future versions. | ||
<b>Note:</b> Other `GeoJSON` types will be supported in future versions. | ||
@@ -303,6 +307,10 @@ __Examples__ | ||
* geoobj - a valid `GeoJSON` object of the following types: `Point`, `LineString` or `Polygon`. | ||
* geoobj - a valid `GeoJSON` object of the following types: `Point`, `LineString`, `Polygon`, `MultiPoint`, `MultiLineString` or `MultiPolygon`. | ||
**Note:** Other `GeoJSON` types will be supported in future versions. | ||
**Note:** The `Feature`, `FeatureCollection` and `GeometryCollection` objects are unsupported. | ||
The logic being that it would make it difficult to work with such deeply nested arrays, and that the primary `GeoJSON` objects | ||
would be mixed. | ||
To convert the above to arrays, rather convert the individual geometries in the feature etc. | ||
__Examples__ | ||
@@ -337,1 +345,53 @@ | ||
``` | ||
## Validations | ||
<a name="isGeoJSON" /> | ||
### isGeoJSON(obj[, returnError]) | ||
Takes an input of an object, and returns a `true` or `false`. Include a `Boolean` to return a validation message for invalid objects. | ||
__Arguments__ | ||
* obj - an object, either valid or invalid `GeoJSON`. | ||
* returnError - a `Boolean` indicating whether to return a validation message if obj is invalid. | ||
The default `returnError` is `true`. | ||
**Note:** All `GeoJSON` types are supported in the `isGeoJSON` check. They are: | ||
* `Point` | ||
* `MultiPoint` | ||
* `LineString` | ||
* `MultiLineString` | ||
* `Polygon` | ||
* `MultiPolygon` | ||
* `Feature` | ||
* `FeatureCollection` | ||
* `GeometryCollection` | ||
Nested GeoJSON objects are validated as part of the supplied object. | ||
#### Caveats | ||
1. We currently do not check the order of `LinearRings` inside a `Polygon` | ||
2. All `coordinates` supplied are expected to be numbers, we do not `parseFloat`s | ||
3. Though the library expects users to be using WGS84, we do not check bounds of lat and lng | ||
__Examples__ | ||
To validate a valid `GeoJSON::Point` | ||
```js | ||
var geoobj = {"type":"Point","coordinates":[30,20]}; | ||
isGeoJSON(geoobj, true); | ||
// true | ||
``` | ||
To validate an invalid `GeoJSON::LineString` | ||
```js | ||
var geoobj = {"type":"LineString","coordinate":[[30,20],[29.5,20.5]]}; | ||
// note the 'coordinate', expecting 'coordinates' | ||
isGeoJSON(geoobj, true); | ||
// {result: false, message: "invalid GeoJSON type supplied"} | ||
``` |
@@ -49,3 +49,3 @@ /** | ||
type = 'LineString'; | ||
} else if (type === 'MultiPoint') { | ||
} else if (type === 'multipoint') { | ||
type = 'MultiPoint'; | ||
@@ -90,3 +90,3 @@ } | ||
if (a.length < 2 && !error) { | ||
error = new Error("Expecting each LineString in MultiiLineString to have at least 2 points."); | ||
error = new Error("Expecting each LineString in MultiLineString to have at least 2 points."); | ||
return true; | ||
@@ -138,3 +138,3 @@ } | ||
default: | ||
error = new Error("type not recognised. Should be 'point', 'linestring', or 'polygon'"); | ||
error = new Error("type not recognised or supported"); | ||
} | ||
@@ -205,4 +205,55 @@ if (error) { | ||
}); | ||
break; | ||
case 'multipoint': | ||
var mpoint; | ||
array = []; | ||
// REVIEW should we check if valid object? Or can we do it at the top? | ||
_.each(geoobj.coordinates, function (pt) { | ||
array.push([parseFloat(pt[1]), parseFloat(pt[0])]); | ||
}); | ||
break; | ||
case 'multilinestring': | ||
var multiline; | ||
array = []; | ||
// check if valid object | ||
_.find(geoobj.coordinates, function (a) { | ||
if (!a.length) { | ||
error = new Error("the object specified is not a valid GeoJSON MultiLineString"); | ||
return true; | ||
} | ||
multiline = []; | ||
_.each(a, function (pl) { | ||
multiline.push([parseFloat(pl[1]), parseFloat(pl[0])]); | ||
}); | ||
array.push(multiline); | ||
return false; | ||
}); | ||
break; | ||
case 'multipolygon': | ||
var poly; | ||
array = []; | ||
_.each(geoobj.coordinates, function (coord) { | ||
_.find(coord, function (a) { | ||
if (!a.length) { | ||
error = new Error("the object specified is not a valid GeoJSON Polygon"); | ||
return true; | ||
} | ||
poly = []; | ||
if (a[0].toString() !== _.last(a).toString()) { | ||
error = new Error("The first and last coordinates of the Polygon are not the same"); | ||
return true; | ||
} | ||
if (a.length < 4) { | ||
error = new Error("A valid Polygon should have a minimum set of 4 points"); | ||
return true; | ||
} | ||
_.each(_.initial(a), function (pl) { | ||
poly.push([parseFloat(pl[1]), parseFloat(pl[0])]); | ||
}); | ||
array.push(poly); | ||
return false; | ||
}); | ||
// return? | ||
}); | ||
break; | ||
default: | ||
@@ -224,7 +275,2 @@ error = new Error("unknown GeoJSON type specified"); | ||
var getDistance = function (array, decimals) { | ||
if (Number.prototype.toRad === undefined) { | ||
Number.prototype.toRad = function () { | ||
return this * Math.PI / 180; | ||
}; | ||
} | ||
@@ -256,6 +302,6 @@ decimals = decimals || 3; | ||
dLat = (lat2 - lat1).toRad(); | ||
dLon = (lon2 - lon1).toRad(); | ||
lat1 = lat1.toRad(); | ||
lat2 = lat2.toRad(); | ||
dLat = _toRadian(lat2 - lat1); | ||
dLon = _toRadian(lon2 - lon1); | ||
lat1 = _toRadian(lat1); | ||
lat2 = _toRadian(lat2); | ||
@@ -340,3 +386,2 @@ a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + | ||
c = [(b[0] - a[0]) * ratio * bearing[0], (b[1] - a[1]) * ratio * bearing[1]]; | ||
// console.log(c); | ||
if (bearing[0] > 0) { | ||
@@ -376,2 +421,192 @@ c[0] = c[0] + a[0]; | ||
/** | ||
* takes an input of an object, and returns a `true` or `false`. Include a `Boolean` | ||
+ to return a validation message for invalid objects. | ||
* | ||
* @param {Object} either a valid LineString, or an array of coordinates | ||
* @param {Boolean} Optional true/false to return error message on invalid object | ||
* @returns {Boolean} either true, false, or an object if `returnError` is not falsy | ||
*/ | ||
var isGeoJSON = function (obj, returnError) { | ||
var validTypes = [ | ||
"Point", | ||
"MultiPoint", | ||
"LineString", | ||
"MultiLineString", | ||
"Polygon", | ||
"MultiPolygon", | ||
"Feature", | ||
"FeatureCollection", | ||
"GeometryCollection" | ||
]; | ||
var invalid; | ||
var index = _.indexOf(validTypes, obj.type); | ||
if (!obj.type || index < 0) { | ||
return _returnError(false, returnError, {message: 'invalid GeoJSON type supplied'}); | ||
} | ||
if (index < 6) { | ||
if (!obj.coordinates || !_.isArray(obj.coordinates)) { | ||
return _returnError(false, returnError, {message: 'invalid GeoJSON type supplied'}); | ||
} | ||
if (obj.type === 'Point') { | ||
// expect a single set of coordinates, or an array where the first 2 elements are numbers | ||
return _isPosition(obj.coordinates, returnError); | ||
} | ||
if (obj.type === 'MultiPoint') { | ||
if (obj.coordinates.length < 2) { | ||
return _returnError(false, returnError, {message: 'expecting array with at least 2 elements for GeoJSON MultiPoint'}); | ||
} | ||
invalid = _.find(obj.coordinates, function (xy) { | ||
return !_isPosition(xy); | ||
}); | ||
if (invalid) { | ||
return _returnError(false, returnError, {message: 'one of the coordinates of the GeoJSON MultiPoint are invalid'}); | ||
} | ||
// GeoJSON MultiPoint is valid | ||
return true; | ||
} | ||
if (obj.type === 'LineString') { | ||
if (obj.coordinates.length < 2) { | ||
return _returnError(false, returnError, {message: 'expecting array with at least 2 elements for GeoJSON LineString'}); | ||
} | ||
return _isLineString(obj.coordinates, returnError); | ||
} | ||
if (obj.type === 'MultiLineString') { | ||
if (obj.coordinates.length < 2) { | ||
return _returnError(false, returnError, {message: 'expecting array of multiple set of coordinates for GeoJSON MultiLineString'}); | ||
} | ||
invalid = _.find(obj.coordinates, function (coordinates) { | ||
return !_isLineString(coordinates, returnError); | ||
}); | ||
if (invalid) { | ||
return _returnError(false, returnError, {message: 'one of the coordinates of the GeoJSON MultiLineString are invalid'}); | ||
} | ||
// GeoJSON MultiLineString is valid | ||
return true; | ||
} | ||
if (obj.type === 'Polygon') { | ||
return _isLinearRing(obj.coordinates, returnError); | ||
} | ||
if (obj.type === 'MultiPolygon') { | ||
invalid = _.find(obj.coordinates, function (coordinates) { | ||
return !_isLinearRing(coordinates, returnError); | ||
}); | ||
if (invalid) { | ||
return _returnError(false, returnError, {message: 'one of the coordinateof the GeoJSON MultiPolygon are invalid'}); | ||
} | ||
// GeoJSON MultiPolygon is valid | ||
return true; | ||
} | ||
} | ||
if (index === 6) { | ||
if (!obj.geometry) { | ||
return _returnError(false, returnError, {message: 'expected GeoJSON Feature to have a geometry'}); | ||
} | ||
if (!obj.properties) { | ||
return _returnError(false, returnError, {message: 'expected GeoJSON Feature to have properties'}); | ||
} | ||
// NOTE that we use recursion here, don't like the practise but we had to do it | ||
// a feature can have an `id` but it is optional, we do not check it. | ||
return isGeoJSON(obj.geometry, returnError); | ||
} | ||
if (index === 7) { | ||
if (!obj.features) { | ||
return _returnError(false, returnError, {message: "expected GeoJSON FeatureCollection to have features"}); | ||
} | ||
if (!_.isArray(obj.features)) { | ||
return _returnError(false, returnError, {message: "expected GeoJSON FeatureCollection's features to be an array"}); | ||
} | ||
invalid = _.find(obj.features, function (feature) { | ||
return !isGeoJSON(feature); | ||
}); | ||
if (invalid) { | ||
return _returnError(false, returnError, {message: "one of the GeoJSON FeatureCollection's features is invalid"}); | ||
} | ||
return true; | ||
} | ||
if (index === 8) { | ||
if (!obj.geometries) { | ||
return _returnError(false, returnError, {message: "expected GeoJSON GeometryCollection to have geometries"}); | ||
} | ||
if (!_.isArray(obj.geometries)) { | ||
return _returnError(false, returnError, {message: 'expected GeoJSON GeometryCollection\'s geometries to be an array'}); | ||
} | ||
var invalid = _.find(obj.geometries, function (geometry) { | ||
return !isGeoJSON(geometry); | ||
}); | ||
if (invalid) { | ||
return _returnError(false, returnError, {message: 'one of the GeoJSON GeometryCollection\'s geometries is invalid'}); | ||
} | ||
// GeoJSON GeometryCollection is valid | ||
return true; | ||
} | ||
}; | ||
var _isLinearRing = function (arr, returnError) { | ||
var invalid = _.find(arr, function (coordinates) { | ||
if (coordinates.length < 4) { | ||
return _returnError(false, returnError, {message: 'expecting coordinates of GeoJSON object to have at least 4 positions'}); | ||
} | ||
if (!_.isEqual(_.last(coordinates), coordinates[0])) { | ||
return _returnError(false, returnError, {message: 'the first and last positions of GeoJSON LinearRing are not the same'}); | ||
} | ||
return _isPosition(coordinates); | ||
}); | ||
if (invalid) { | ||
return _returnError(false, returnError, {message: ''}) | ||
} | ||
return true; | ||
}; | ||
var _isPosition = function (coordinates, returnError) { | ||
if (coordinates.length < 2 || !_.isNumber(coordinates[0]) || !_.isNumber(coordinates[1])) { | ||
return _returnError(false, returnError, {message: 'invalid coordinates for GeoJSON Point'}); | ||
} | ||
// Position is valid | ||
return true; | ||
}; | ||
var _isLineString = function (coordinates, returnError) { | ||
var invalid = _.find(coordinates, function (xy) { | ||
return !_isPosition(xy); | ||
}); | ||
if (invalid) { | ||
return _returnError(false, returnError, {message: 'one of the coordinates of the LineString are invalid'}); | ||
} | ||
// GeoJSON LineString coordinates are valid | ||
return true; | ||
}; | ||
/** | ||
* converts degrees to radians | ||
* | ||
* @param {Number} coordinates in degrees | ||
* @returns {Number} coordinates in radians | ||
*/ | ||
var _toRadian = function (degree) { | ||
return decimal * Math.PI / 180; | ||
} | ||
/** | ||
* returns an object with result, and error message if provided | ||
* | ||
* @param {Boolean} error, can also take any JavaScript object | ||
* @param {Boolean} indicator of whether to return error message | ||
* @param {Object} object containing error message | ||
* @returns {Object} result and error message | ||
*/ | ||
var _returnError = function (err, returnError, options) { | ||
if (!returnError) { | ||
return err; | ||
} | ||
if (!options) { | ||
options = {}; | ||
} | ||
if (options.message) { | ||
return {result: err, message: options.message}; | ||
} | ||
return {result: err}; | ||
}; | ||
/* | ||
@@ -383,2 +618,3 @@ * Export functions | ||
exports.getDistance = getDistance; | ||
exports.complexify = complexify; | ||
exports.complexify = complexify; | ||
exports.isGeoJSON = isGeoJSON; |
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
30619
594
1
393
Updatedunderscore@^1.8.3