Skip to content
This repository has been archived by the owner on May 29, 2024. It is now read-only.

Commit

Permalink
init fixtures, code updates to support IETF spec
Browse files Browse the repository at this point in the history
  • Loading branch information
perrygeo committed Jul 7, 2016
1 parent c3b44f4 commit 957389f
Show file tree
Hide file tree
Showing 70 changed files with 431 additions and 153 deletions.
204 changes: 175 additions & 29 deletions object.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,18 @@ function hint(gj, options) {
line: _.__line__
});
} else if (!types[_.type]) {
errors.push({
message: 'The type ' + _.type + ' is unknown',
line: _.__line__
});
} else {
if (typesLower.indexOf(_.type.toLowerCase()) > -1) {
errors.push({
message: 'GeoJSON types are case sensitive',
line: _.__line__
});
} else {
errors.push({
message: 'The type ' + _.type + ' is unknown',
line: _.__line__
});
}
} else if (_) {
types[_.type](_);
}
}
Expand Down Expand Up @@ -70,6 +77,13 @@ function hint(gj, options) {
function FeatureCollection(featureCollection) {
crs(featureCollection);
bbox(featureCollection);
if (featureCollection.properties !== undefined ||
featureCollection.coordinates !== undefined) {
errors.push({
message: 'FeatureCollection object cannot contain properties or coordinates',
line: featureCollection.__line__
});
}
if (!requiredProperty(featureCollection, 'features', 'array')) {
if (!everyIs(featureCollection.features, 'object')) {
return errors.push({
Expand All @@ -96,12 +110,39 @@ function hint(gj, options) {
line: _.__line__ || line
});
}
if (_.length > 3) {
return errors.push({
message: 'position should not have more than 3 elements',
line: _.__line__ || line
});
}
if (!everyIs(_, 'number')) {
return errors.push({
message: 'each element in a position must be a number',
line: _.__line__ || line
});
}

// This is an arbitrary threshold roughly corresponding to
// the upper limit of survey-grade GPS precision in CRS84
// anything more and we're into microscopy
var maxPrecision = 9;
var num;
for (var i = 0; i < _.length; i++) {
num = _[i];
// TODO there has got to be a better way. Check original text?
// By this point number has already been parsed to a float...
var precision = 0;
var decimalStr = (num + "").split(".")[1]
if (decimalStr !== undefined)
precision = decimalStr.length;
if (precision > maxPrecision) {
return errors.push({
message: "precision of coordinates should be reduced",
line: _.__line__ || line
});
}
}
}
}

Expand All @@ -115,10 +156,11 @@ function hint(gj, options) {
if (depth === 1 && type) {
if (type === 'LinearRing') {
if (!Array.isArray(coords[coords.length - 1])) {
return errors.push({
errors.push({
message: 'a number was found where a coordinate array should have been found: this needs to be nested more deeply',
line: line
});
return true;
}
if (coords.length < 4) {
errors.push({
Expand All @@ -129,15 +171,15 @@ function hint(gj, options) {
if (coords.length &&
(coords[coords.length - 1].length !== coords[0].length ||
!coords[coords.length - 1].every(function(position, index) {
return coords[0][index] === position;
return coords[0][index] === position;
}))) {
errors.push({
message: 'the first and last positions in a LinearRing of coordinates must be the same',
line: line
});
}
} else if (type === 'Line' && coords.length < 2) {
errors.push({
return errors.push({
message: 'a line needs to have two or more coordinates to be valid',
line: line
});
Expand All @@ -155,27 +197,76 @@ function hint(gj, options) {
}
}


function rightHandRule (geometry) {
var rhr = true;
if (geometry.type == 'Polygon') {
rhr = isPolyRHR(geometry.coordinates);
} else if (geometry.type == 'MultiPolygon') {
for (var i = 0; i < geometry.coordinates.length; i++) {
if (!isPolyRHR(geometry.coordinates[i])) {
rhr = false;
break;
}
}
}
if (!rhr) {
errors.push({
message: 'Polygons and MultiPolygons should follow the right-hand rule',
line: geometry.__line__
});
}
};

function isPolyRHR (coords) {
if (coords && coords.length > 0) {
if (!isRingClockwise(coords[0]))
return false;
for (var i = 1; i < coords.length; i++) {
if (isRingClockwise(coords[i]))
return false;
}
}
return true;
};

function isRingClockwise (coords) {
var area = 0;
if (coords.length > 2) {
var p1, p2;
for (var i = 0; i < coords.length - 1; i++) {
p1 = coords[i];
p2 = coords[i + 1];
area += rad(p2[0] - p1[0]) * (2 + Math.sin(rad(p1[1])) + Math.sin(rad(p2[1])));
}
}

if (area >= 0)
return true;
else
return false;
};

function rad(x) {
return x * Math.PI / 180;
}

function crs(_) {
if (!_.crs) return;
if (typeof _.crs === 'object') {
var strErr = requiredProperty(_.crs, 'type', 'string'),
propErr = requiredProperty(_.crs, 'properties', 'object');
if (!strErr && !propErr) {
// http://geojson.org/geojson-spec.html#named-crs
if (_.crs.type === 'name') {
requiredProperty(_.crs.properties, 'name', 'string');
} else if (_.crs.type === 'link') {
requiredProperty(_.crs.properties, 'href', 'string');
} else {
errors.push({
message: 'The type of a crs must be either "name" or "link"',
line: _.__line__
});
}
}
var defaultCRS = {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
}
if (typeof _.crs === 'object' && _.crs.properties && _.crs.properties.name == defaultCRS.properties.name) {
errors.push({
message: "crs is not a valid GeoJSON type, this object is equivalent to the default and should be removed",
line: _.__line__
});
} else {
errors.push({
message: 'the value of the crs property must be an object, not a ' + (typeof _.crs),
message: "crs is not a valid GeoJSON type",
line: _.__line__
});
}
Expand All @@ -185,11 +276,18 @@ function hint(gj, options) {
if (!_.bbox) { return; }
if (Array.isArray(_.bbox)) {
if (!everyIs(_.bbox, 'number')) {
return errors.push({
errors.push({
message: 'each element in a bbox property must be a number',
line: _.bbox.__line__
});
}
if (!(_.bbox.length == 4 || _.bbox.length == 6)) {
errors.push({
message: 'bbox must contain 4 elements (for 2D) or 6 elements (for 3D)',
line: _.bbox.__line__
});
}
return errors.length
} else {
errors.push({
message: 'bbox property must be an array of numbers, but is a ' + (typeof _.bbox),
Expand All @@ -198,10 +296,22 @@ function hint(gj, options) {
}
}

function geometrySemantics(geom) {
if (geom.properties !== undefined ||
geom.geometry !== undefined ||
geom.features !== undefined) {
errors.push({
message: 'geometry object cannot contain features, geometry or properties',
line: geom.__line__
});
}
}

// http://geojson.org/geojson-spec.html#point
function Point(point) {
crs(point);
bbox(point);
geometrySemantics(point);
if (!requiredProperty(point, 'coordinates', 'array')) {
position(point.coordinates);
}
Expand All @@ -212,7 +322,9 @@ function hint(gj, options) {
crs(polygon);
bbox(polygon);
if (!requiredProperty(polygon, 'coordinates', 'array')) {
positionArray(polygon.coordinates, 'LinearRing', 2);
if (!positionArray(polygon.coordinates, 'LinearRing', 2)) {
rightHandRule(polygon);
}
}
}

Expand All @@ -221,7 +333,9 @@ function hint(gj, options) {
crs(multiPolygon);
bbox(multiPolygon);
if (!requiredProperty(multiPolygon, 'coordinates', 'array')) {
positionArray(multiPolygon.coordinates, 'LinearRing', 3);
if (!positionArray(multiPolygon.coordinates, 'LinearRing', 3)) {
rightHandRule(multiPolygon);
}
}
}

Expand Down Expand Up @@ -262,8 +376,22 @@ function hint(gj, options) {
line: geometryCollection.__line__
});
}
if (geometryCollection.geometries.length == 1) {
errors.push({
message: 'GeometryCollection with a single geometry should be avoided in favor of single part or a single object of multi-part type',
line: geometryCollection.geometries.__line__
});
}
geometryCollection.geometries.forEach(function(geometry) {
if (geometry) root(geometry);
if (geometry) {
if (geometry.type === "GeometryCollection") {
errors.push({
message: "GeometryCollection should avoid nested geometry collections",
line: geometryCollection.geometries.__line__
});
}
root(geometry);
}
});
}
}
Expand All @@ -280,6 +408,12 @@ function hint(gj, options) {
line: feature.__line__
});
}
if (feature.features !== undefined || feature.coordinates !== undefined) {
errors.push({
message: 'Feature object cannot contain features or coordinates',
line: feature.__line__
});
}
if (feature.type !== 'Feature') {
errors.push({
message: 'GeoJSON features must have a type=feature property',
Expand All @@ -306,6 +440,18 @@ function hint(gj, options) {
MultiPolygon: MultiPolygon
};

function propertiesLowerCase(obj) {
var props = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
props.push(prop.toLowerCase());
}
}
return props;
}

var typesLower = propertiesLowerCase(types);

if (typeof gj !== 'object' ||
gj === null ||
gj === undefined) {
Expand Down
4 changes: 4 additions & 0 deletions test/data/bad/bad-duplicate-properties.result
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"message": "An object contained duplicate members, making parsing ambigous: type",
"line": 1
},
{
"message": "Feature object cannot contain features or coordinates",
"line": 1
},
{
"message": "\"properties\" property required",
"line": 1
Expand Down
5 changes: 4 additions & 1 deletion test/data/bad/bad-duplicate-properties.result-object
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
[
{
"message": "Feature object cannot contain features or coordinates"
},
{
"message": "\"properties\" property required"
},
{
"message": "\"geometry\" property required"
}
]
]
6 changes: 5 additions & 1 deletion test/data/bad/bad-polygonloop.result
Original file line number Diff line number Diff line change
Expand Up @@ -1442,5 +1442,9 @@
{
"message": "a number was found where a coordinate array should have been found: this needs to be nested more deeply",
"line": 1445
},
{
"message": "Polygons and MultiPolygons should follow the right-hand rule",
"line": 2
}
]
]
2 changes: 1 addition & 1 deletion test/data/bad/badfeatureid.geojson
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"type": "Point",
"coordinates": [
-100.8984375,
39.36827914916011
39.368279
]
}
}
Expand Down
5 changes: 5 additions & 0 deletions test/data/bad/bbox-4or6elements.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "Point",
"coordinates": [2, 2],
"bbox": [1, 2, 3]
}
1 change: 1 addition & 0 deletions test/data/bad/bbox-4or6elements.result
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"message":"bbox must contain 4 elements (for 2D) or 6 elements (for 3D)","line":4}]
1 change: 1 addition & 0 deletions test/data/bad/bbox-4or6elements.result-object
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"message":"bbox must contain 4 elements (for 2D) or 6 elements (for 3D)"}]
2 changes: 1 addition & 1 deletion test/data/bad/bbox-contains-string.geojson
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "Point",
"coordinates": [2, 2],
"bbox": [1, 2, "string"]
"bbox": [1, 2, 3, "4"]
}
5 changes: 0 additions & 5 deletions test/data/bad/crs-badroot.geojson

This file was deleted.

Loading

1 comment on commit 957389f

@tyrasd
Copy link
Contributor

@tyrasd tyrasd commented on 957389f Nov 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the right hand rule logic above really correct? I'm seeing some strange results, see #54

Please sign in to comment.