Skip to content

Commit

Permalink
fix: add bounding box validity checking to geometry core functions an…
Browse files Browse the repository at this point in the history
…d matchers

The spec for [bounding boxes](https://datatracker.ietf.org/doc/html/rfc7946#section-5) permits
GeoJSON objects to have bounding boxes. Section 3 of the spec states "A GeoJSON object represents a
Geometry, Feature, or collection of Features." Therefore, the geometry objects should all have
allowed bounding boxes. Thus, they all need to check that if that property is present that it is in
fact a valid 2D or 3D bbox.

Resolves: #9, #10, #11, #12, #13, #14, #16, #29
  • Loading branch information
M-Scott-Lassiter committed May 31, 2022
1 parent 365c2fc commit ac6a9a1
Show file tree
Hide file tree
Showing 15 changed files with 650 additions and 8 deletions.
8 changes: 6 additions & 2 deletions src/core/geometries/anyGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { lineStringGeometry } = require('./lineStringGeometry')
const { multiLineStringGeometry } = require('./multiLineStringGeometry')
const { polygonGeometry } = require('./polygonGeometry')
const { multiPolygonGeometry } = require('./multiPolygonGeometry')
// const { geometryCollection } = require('./geometryCollection')
const { validBoundingBox } = require('../boundingBoxes/validBoundingBox')

/**
* Verifies an object meets validity requirements for one of the six basic GeoJSON geometry types:
Expand Down Expand Up @@ -59,7 +59,7 @@ const { multiPolygonGeometry } = require('./multiPolygonGeometry')
* const badExample = anyGeometry(feature)) // throws error
*/
function anyGeometry(geometryObject) {
// The three prohibited properties below account for nested GeometryCollection objects. All other geometry
// The bbox and three prohibited properties below account for nested GeometryCollection objects. All other geometry
// core functions include these within them, including geometryCollection.js.
// Note: This might be a good set of checks to abstract away in a utility function later...
if ('geometry' in geometryObject) {
Expand All @@ -76,6 +76,10 @@ function anyGeometry(geometryObject) {
throw new Error(`GeoJSON Geometry objects are forbidden from having a property 'features'.`)
}

if ('bbox' in geometryObject) {
validBoundingBox(geometryObject.bbox)
}

if (geometryObject?.type === 'Point') {
pointGeometry(geometryObject)
return true
Expand Down
6 changes: 6 additions & 0 deletions src/core/geometries/geometryCollection.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { anyGeometry } = require('./anyGeometry')
const { validBoundingBox } = require('../boundingBoxes/validBoundingBox')

/**
* Verifies an object is a valid GeoJSON GeometryCollection. This object requires a
Expand All @@ -8,6 +9,7 @@ const { anyGeometry } = require('./anyGeometry')
* The geometries may be an empty array, but may not be an array of empty arrays or objects.
*
* Foreign members are allowed with the exceptions thrown below.
* If present, bounding boxes must be valid.
*
* @memberof Core.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/16
Expand Down Expand Up @@ -87,6 +89,10 @@ function geometryCollection(geometryObject) {
)
}

if ('bbox' in geometryObject) {
validBoundingBox(geometryObject.bbox)
}

if (!Array.isArray(geometryObject.geometries)) {
throw new Error('Geometries property must be an array of valid GeoJSON geometry objects.')
}
Expand Down
8 changes: 7 additions & 1 deletion src/core/geometries/lineStringGeometry.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { validCoordinate } = require('../coordinates/validCoordinate')
const { validBoundingBox } = require('../boundingBoxes/validBoundingBox')

/**
* Verifies an object is a valid GeoJSON LineString Geometry. This geometry requires a
Expand All @@ -7,14 +8,15 @@ const { validCoordinate } = require('../coordinates/validCoordinate')
* but may not be an array of empty arrays.
*
* Foreign members are allowed with the exceptions thrown below.
* If present, bounding boxes must be valid.
*
* @memberof Core.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/11
* @param {object} geometryObject a GeoJSON LineString Geometry object
* @returns {boolean} True if a valid GeoJSON LineString Geometry. If invalid, it will throw an error.
* @throws {Error} Argument not an object
* @throws {Error} Must have a type property with value 'LineString'
* @throws {Error} forbidden from having a property 'geometry', 'properties', or 'features'
* @throws {Error} Forbidden from having a property 'geometry', 'properties', or 'features'
* @example
* const linestring = {
* type: 'LineString',
Expand Down Expand Up @@ -69,6 +71,10 @@ function lineStringGeometry(geometryObject) {
)
}

if ('bbox' in geometryObject) {
validBoundingBox(geometryObject.bbox)
}

// Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
if (!Array.isArray(geometryObject.coordinates) && geometryObject.coordinates.length !== 1) {
Expand Down
8 changes: 7 additions & 1 deletion src/core/geometries/multiLineStringGeometry.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { validCoordinate } = require('../coordinates/validCoordinate')
const { validBoundingBox } = require('../boundingBoxes/validBoundingBox')

/**
* Verifies an object is a valid GeoJSON MultiLineString Geometry. This geometry requires a
Expand All @@ -9,6 +10,7 @@ const { validCoordinate } = require('../coordinates/validCoordinate')
* The coordinates may be an empty array, but may not be an array of empty arrays.
*
* Foreign members are allowed with the exceptions thrown below.
* If present, bounding boxes must be valid.
*
* @memberof Core.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/12
Expand All @@ -17,7 +19,7 @@ const { validCoordinate } = require('../coordinates/validCoordinate')
* @throws {Error} Argument not an object
* @throws {Error} Must have a type property with value 'MultiLineString'
* @throws {Error} Coordinates array must contain two or more valid GeoJSON coordinates
* @throws {Error} forbidden from having a property 'geometry', 'properties', or 'features'
* @throws {Error} Forbidden from having a property 'geometry', 'properties', or 'features'
* @example
* const multiLineString = {
* type: 'MultiLineString',
Expand Down Expand Up @@ -85,6 +87,10 @@ function multiLineStringGeometry(geometryObject) {
)
}

if ('bbox' in geometryObject) {
validBoundingBox(geometryObject.bbox)
}

// Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
if (!Array.isArray(geometryObject.coordinates) && geometryObject.coordinates.length !== 1) {
Expand Down
8 changes: 7 additions & 1 deletion src/core/geometries/multiPointGeometry.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { validCoordinate } = require('../coordinates/validCoordinate')
const { validBoundingBox } = require('../boundingBoxes/validBoundingBox')

/**
* Verifies an object is a valid GeoJSON MultiPoint Geometry. This geometry requires a
Expand All @@ -7,14 +8,15 @@ const { validCoordinate } = require('../coordinates/validCoordinate')
* The coordinates may be an empty array.
*
* Foreign members are allowed with the exceptions thrown below.
* If present, bounding boxes must be valid.
*
* @memberof Core.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/10
* @param {object} geometryObject a GeoJSON MultiPoint Geometry object
* @returns {boolean} True if a valid GeoJSON MultiPoint Geometry. If invalid, it will throw an error.
* @throws {Error} Argument not an object
* @throws {Error} Must have a type property with value 'MultiPoint'
* @throws {Error} forbidden from having a property 'geometry', 'properties', or 'features'
* @throws {Error} Forbidden from having a property 'geometry', 'properties', or 'features'
* @example
* const testMultiPoint1 = {
* type: 'MultiPoint',
Expand Down Expand Up @@ -66,6 +68,10 @@ function multiPointGeometry(geometryObject) {
)
}

if ('bbox' in geometryObject) {
validBoundingBox(geometryObject.bbox)
}

// Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
if (!Array.isArray(geometryObject.coordinates) && geometryObject.coordinates.length !== 1) {
Expand Down
8 changes: 7 additions & 1 deletion src/core/geometries/multiPolygonGeometry.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { validCoordinate } = require('../coordinates/validCoordinate')
const { validBoundingBox } = require('../boundingBoxes/validBoundingBox')

/**
* Verifies an object is a valid GeoJSON MultiPolygon Geometry. This geometry requires a
Expand All @@ -9,6 +10,7 @@ const { validCoordinate } = require('../coordinates/validCoordinate')
* The coordinates may be an empty array, but may not be an array of empty arrays.
*
* Foreign members are allowed with the exceptions thrown below.
* If present, bounding boxes must be valid.
*
* @memberof Core.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/14
Expand All @@ -18,7 +20,7 @@ const { validCoordinate } = require('../coordinates/validCoordinate')
* @throws {Error} Must have a type property with value 'MultiPolygon'
* @throws {Error} Coordinates array must contain four or more valid GeoJSON coordinates
* @throws {Error} Final coordinate must match first coordinate
* @throws {Error} forbidden from having a property 'geometry', 'properties', or 'features'
* @throws {Error} Forbidden from having a property 'geometry', 'properties', or 'features'
* @example
* const multiPolygon = {
* type: 'MultiPolygon',
Expand Down Expand Up @@ -109,6 +111,10 @@ function multiPolygonGeometry(geometryObject) {
)
}

if ('bbox' in geometryObject) {
validBoundingBox(geometryObject.bbox)
}

// Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
if (!Array.isArray(geometryObject.coordinates)) {
Expand Down
8 changes: 7 additions & 1 deletion src/core/geometries/pointGeometry.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
const { validCoordinate } = require('../coordinates/validCoordinate')
const { validBoundingBox } = require('../boundingBoxes/validBoundingBox')

/**
* Verifies an object is a valid GeoJSON Point Geometry. This geometry requires a
* 'type' property that must equal "Point", and a 'coordinates' property that contains
* a single valid WGS-84 GeoJSON coordinate. The coordinates may be an empty array.
*
* Foreign members are allowed with the exceptions thrown below.
* If present, bounding boxes must be valid.
*
* @memberof Core.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/9
* @param {object} geometryObject a GeoJSON Point Geometry object
* @returns {boolean} True if a valid GeoJSON Point Geometry. If invalid, it will throw an error.
* @throws {Error} Argument not an object
* @throws {Error} Must have a type property with value 'Point'
* @throws {Error} forbidden from having a property 'geometry', 'properties', or 'features'
* @throws {Error} Forbidden from having a property 'geometry', 'properties', or 'features'
* @example
* const testPoint = {
* type: 'Point',
Expand Down Expand Up @@ -63,6 +65,10 @@ function pointGeometry(geometryObject) {
)
}

if ('bbox' in geometryObject) {
validBoundingBox(geometryObject.bbox)
}

// Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
if (Array.isArray(geometryObject.coordinates) && geometryObject.coordinates.length === 0) {
Expand Down
8 changes: 7 additions & 1 deletion src/core/geometries/polygonGeometry.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { validCoordinate } = require('../coordinates/validCoordinate')
const { validBoundingBox } = require('../boundingBoxes/validBoundingBox')

/**
* Verifies an object is a valid GeoJSON Polygon Geometry. This geometry requires a
Expand All @@ -9,6 +10,7 @@ const { validCoordinate } = require('../coordinates/validCoordinate')
* The coordinates may be an empty array, but may not be an array of empty arrays.
*
* Foreign members are allowed with the exceptions thrown below.
* If present, bounding boxes must be valid.
*
* @memberof Core.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/13
Expand All @@ -18,7 +20,7 @@ const { validCoordinate } = require('../coordinates/validCoordinate')
* @throws {Error} Must have a type property with value 'Polygon'
* @throws {Error} Coordinates array must contain four or more valid GeoJSON coordinates
* @throws {Error} Final coordinate must match first coordinate
* @throws {Error} forbidden from having a property 'geometry', 'properties', or 'features'
* @throws {Error} Forbidden from having a property 'geometry', 'properties', or 'features'
* @example
* const polygon = {
* type: 'Polygon',
Expand Down Expand Up @@ -88,6 +90,10 @@ function polygonGeometry(geometryObject) {
)
}

if ('bbox' in geometryObject) {
validBoundingBox(geometryObject.bbox)
}

// Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
if (!Array.isArray(geometryObject.coordinates)) {
Expand Down
88 changes: 88 additions & 0 deletions tests/geometries/toBeGeometryCollection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,68 @@ describe('Valid Use Cases', () => {
expect(testGeometryCollection).toBeGeometryCollection()
expect(testGeometryCollection).toBeAnyGeometry()
})

describe('Bounding Boxes Allowed, Must be Valid:', () => {
test('2D Bounding Box', () => {
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [
{
type: 'Point',
coordinates: [0, 0]
}
],
bbox: [-10.0, -10.0, 10.0, 10.0]
}
expect(testGeometryCollection).toBeGeometryCollection()
expect(testGeometryCollection).toBeAnyGeometry()
})

test('3D Bounding Box', () => {
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [
{
type: 'Point',
coordinates: [0, 0]
}
],
bbox: [-10.0, -10.0, 10.0, 10.0]
}
expect(testGeometryCollection).toBeGeometryCollection()
expect(testGeometryCollection).toBeAnyGeometry()
})

test('Illogical Bounding Box', () => {
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [
{
type: 'Point',
coordinates: [0, 0]
}
],
bbox: [-30.0, -30.0, -20.0, -20.0]
}
expect(testGeometryCollection).toBeGeometryCollection()
expect(testGeometryCollection).toBeAnyGeometry()
})

test('Redundant Bounding Box', () => {
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [
{
type: 'Point',
coordinates: [0, 0]
}
],
bbox: [0, 0, 0, 0]
}
expect(testGeometryCollection).toBeGeometryCollection()
expect(testGeometryCollection).toBeAnyGeometry()
})
})
})

describe('Inalid Use Cases', () => {
Expand Down Expand Up @@ -610,6 +672,32 @@ describe('Inalid Use Cases', () => {
expect(testGeometryCollection).not.toBeAnyGeometry()
})
})

describe('Invalid Bounding Boxes Not Allowed:', () => {
const invalidBBoxes = [
[null],
[undefined],
[[]],
[[-10.0, -10.0, 10.0]],
[[-10.0, -10.0, 190.0, 10.0]],
[[-10.0, 10.0, 10.0, -10]],
[[-10.0, -10.0, 0, 10, 10.0, '200']]
]
test.each(invalidBBoxes)('bbox: %p', (input) => {
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [
{
type: 'Point',
coordinates: [180, -90, 2000]
}
],
bbox: input
}
expect(testGeometryCollection).not.toBeGeometryCollection()
expect(testGeometryCollection).not.toBeAnyGeometry()
})
})
})

describe('Error Snapshot Testing. Throws error:', () => {
Expand Down
Loading

0 comments on commit ac6a9a1

Please sign in to comment.