Coder Social home page Coder Social logo

m-scott-lassiter / jest-geojson Goto Github PK

View Code? Open in Web Editor NEW
9.0 2.0 0.0 2.64 MB

GeoJSON Validation Matchers for Jest

Home Page: https://m-scott-lassiter.github.io/jest-geojson/

License: MIT License

JavaScript 99.99% Shell 0.01%
assertions geojson jest jest-tests matchers test testing jest-matchers jest-geojson gis

jest-geojson's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

jest-geojson's Issues

toHaveID

Description

GeoJSON Features have an optional ID member that can be any string or number.

If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.2

An "id" member on a non-feature object gets treated as a foreign member instead of an ID.

This matcher will take an optional argument of either a string, number, RegExp, or array of any combination of these. If no argument is provided, the matcher will check if the Feature object has an ID member of any value. Otherwise, it will check that the ID member exactly matches either the single input or any of the array values.

Valid GeoJSON Examples

{
    "type": "Feature",
    "id": "f1",
    "geometry": {...},
    "properties": {...}
}
Example Matcher Usage
const testFeature = {
    type: 'Feature',
    id: 'f1',
    geometry: {...},
    properties: {...}
}

expect(testFeature).toHaveID()
expect(testFeature).toHaveID('f1')
expect(testFeature).toHaveID([ 1, 'F', 'F12', /[a-z]+[0-9]+/])

expect(testFeature).not.toHaveID(123)

Passing Tests

Numeric or String ID

Input:

  • 0

  • -200

  • 200

  • Infinity

  • -Infinity

  • '1'

  • 'Test 123'

  • 'Random ID String'

  • Empty string: ''

  • const testFeature = {
      type: 'Feature',
      id: <input>,
      geometry: null,
      properties: null
    }
    
  • Using the matcher without an optional argument

  • Using the matcher with the input argument

  • Using the matcher with the input argument as a single element array

  • Using the matcher with the input argument as a RegExp

  • Using the matcher with the input argument as a single element array as a RegExp

Empty Array for Optional Argument

Input: 'Test ID', 3

const testFeature = {
    type: 'Feature',
    id: 'Test ID',
    geometry: null,
    properties: null
}

expect(feature).toHaveStringID([])

Multiple Array Value Checking for Numeric ID

Only one element passes:

  • [1, 2, 3, 719]
  • ['1', 719, '2', '3']
  • [/[0-9]+/, 2, 3, 'F1']
  • [755, /71[0-9]/]

More than one element passes:

  • [/72[0-9]/, /81[0-9]/, /71[0-9]/, /[0-9]+/]
const testFeature = {
    type: 'Feature',
    id: 719,
    geometry: null,
    properties: null
}

Multiple Array Value Checking for String ID

const testFeature = {
    type: 'Feature',
    id: 'Some String',
    geometry: null,
    properties: null
}

Only one element passes:

  • ['1', 719, '2', 3, 'Some String']
  • ['A String ID', /Some/, 2]
  • [/SomeString/, /123/, /\bString\b/]

More than one element passes:

  • [/72[0-9]/, /\bString\b/, /71[0-9]/, /Some/]

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects
  • FeatureCollection object
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • '',
  • 'Random Feature',
  • JSON.stringify({
        type: 'Feature',
        geometry: null,
        properties: null
    })

Valid Feature Does Not Have ID

const testFeature = {
    type: 'Feature',
    geometry: null,
    properties: null
}

Invalid Feature Provided

Invalid features should throw the toBeFeature errors instead of new ones here.

Invalid Inputs To Optional Argument

Rejects when the optional ID to check is

  • Each of the seven Geometry objects
  • Feature or FeatureCollection object
  • undefined, null, false, true
  • { someProp: 'I am not GeoJSON', id: 4 }
  • an empty object: {}
  • NaN

Rejects when ID Does Not Match Optional Input Value

  • String ID that does not match
  • Array of String IDs that do not match
  • Number ID that does not match
  • Array of Number IDs that do not match
  • RegExp that does not match
  • Array of RegExp that does not match

toContainUniqueIDs

Description

GeoJSON Features have an optional ID member that can be any string or number. This matcher verifies that all Features within a FeatureCollection contain valid IDs that are all unique.

If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.2

An "id" member on a non-feature object gets treated as a foreign member instead of an ID. Therefore, this matcher rejects non-FeatureCollection objects.

This matcher takes an optional boolean argument, allowNulls. It will default to false. If set to true, the matcher ignores features that do not have an "id" member, or have the value of "id" explicitly set to null.

This test will fail if any of the features repeat a repeated ID, or if the "features" member is an empty array.

Example Matcher Usage

const testCollection1 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 1
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 2
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 3
    }]
}
const testCollection2 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: "Test 1"
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 2
    }]
}
const testCollection3 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 700
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 700
    }]
}
const testCollection4 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 711
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
    }]
}

expect(testCollection1).toContainUniqueIDs()
expect(testCollection2).toContainUniqueIDs()
expect(testCollection4).toContainUniqueIDs(true)

expect(badTestCollection1).not.toContainUniqueIDs()
expect(badTestCollection2).not.toContainUniqueIDs()
expect(badTestCollection3).not.toContainUniqueIDs()
expect(badTestCollection4).not.toContainUniqueIDs()
expect(badTestCollection4).not.toContainUniqueIDs(false)

Passing Tests

Unique IDs

For both Strings, Numbers, and Combinations:

  • FeatureCollection with Single Feature with ID
  • FeatureCollection with Multiple Features and unique IDs
  • Stress Test: FeatureCollection with 100 Features and unique IDs

Only Some Have IDs, the Rest Null or Not Mentioned, allowNulls is True

For both Strings, Numbers, and Combinations:

  • FeatureCollection with Multiple Features, 1 has no "id" member
  • FeatureCollection with Multiple Features, 1 has an "id" member with null
  • FeatureCollection with Multiple Features, several have no "id" member
  • FeatureCollection with Multiple Features, several have an "id" member with null

FeatureCollection with No ID, allowNulls is True

  • Single feature that has no ID.
  • Single feature that has ID of null.
  • Multiple features that has no ID.
  • Multiple features that has ID of null.

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects
  • FeatureCollection object
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • '',
  • 'Random Feature',
  • JSON.stringify({
          type: 'FeatureCollection',
          features: []
      })

Identical IDs

For both Strings, Numbers, and Combinations:

  • FeatureCollection with Multiple Features and some repeated IDs
  • FeatureCollection with Multiple Features and all repeated IDs

Only Some Have IDs, the Rest Null or Not Mentioned, allowNulls is False

For both Strings, Numbers, and Combinations:

  • FeatureCollection with 5 Features, 1 has no "id" member
  • FeatureCollection with 5 Features, 1 has an "id" member with null

FeatureCollection with Empty Features Array

Should fail because there are no features to check IDs on.

FeatureCollection with No ID, allowNulls is False

  • Single feature that has no ID.
  • Single feature that has ID of null.
  • Multiple features that has no ID.
  • Multiple features that has ID of null.

toBeAnyGeometry

Description

This matcher takes as an argument of an object and checks if it meets validity requirements for any of the seven geometry types:

See the above links for examples.

Example Matcher Usage

point = {
    "type": "Point",
    "coordinates": [100.0, 0.0]
}
lineString = {
    "type": "LineString",
    "coordinates": [
        [
            [180.0, 40.0],
            [180.0, 50.0],
            [170.0, 50.0],
            [170.0, 40.0],
            [180.0, 40.0]
        ]
    ]
}
polygon = {
    "type": "Polygon",
    "coordinates": [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]
    ]
}
feature = {
    "type": "Feature",
    "geometry": {
        "type": "Point",
        "coordinates": [102.0, 0.5]
    }
}

expect(point).toBeAnyGeometry()
expect(lineString).toBeAnyGeometry()
expect(polygon).toBeAnyGeometry()

expect(feature).not.toBeAnyGeometry()
expect([322, -34.549, 0]).not.toBeAnyGeometry()
expect({coordinates: [22, -34.549, 22]}).not.toBeAnyGeometry()

Repeat Tests Contained in Other Files

This matcher works with any geometry. Therefore, the passing and failing tests for each of the seven can also include tests for toBeAnyGeometry.

Unique Tests

  • Rejects Feature
  • Rejects FeatureCollection
  • Snapshots

toContainIDs

Description

GeoJSON Features have an optional ID member that can be any string or number. This matcher verifies that all Features within a FeatureCollection contain valid IDs.

If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.2

An "id" member on a non-feature object gets treated as a foreign member instead of an ID. Therefore, this matcher rejects non-FeatureCollection objects.

This test will fail if any of the features do not have an ID, or if the "features" member is an empty array.

Example Matcher Usage

const goodTtestCollection1 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 1
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 2
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 3
    }]
}
const goodTtestCollection2 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: "Test 1"
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 2
    }]
}
const badTestCollection1 = {
    type: "FeatureCollection",
    features: []
}
const badTestCollection2 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 11
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
    }]
}

expect(goodTtestCollection1).toContainNumericIDs()
expect(goodTtestCollection2).toContainNumericIDs()

expect(badTestCollection1).not.toContainNumericIDs()
expect(badTestCollection2).not.toContainNumericIDs()
expect(goodTtestCollection1.features[0]).not.toContainNumericIDs()

Passing Tests

Unique IDs

For both Strings, Numbers, and Combinations:

  • FeatureCollection with Single Feature with ID
  • FeatureCollection with 5 Features and unique IDs
  • Stress Test: FeatureCollection with 100 Features and unique IDs

Identical IDs

For both Strings, Numbers, and Combinations:

  • FeatureCollection with 5 Features and some repeated IDs
  • FeatureCollection with 5 Features and all repeated IDs

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects
  • FeatureCollection object
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • '',
  • 'Random Feature',
  • JSON.stringify({
          type: 'FeatureCollection',
          features: []
      })

Only Some Have IDs, the Rest Null or Not Mentioned

For both Strings, Numbers, and Combinations:

  • FeatureCollection with 5 Features, 1 has no "id" member
  • FeatureCollection with 5 Features, 1 has an "id" member with null

FeatureCollection with Empty Features Array

Should fail because there are no features to check IDs on.

FeatureCollection with No ID

  • Should fail with a single feature that has no ID.
  • Should fail with a single feature that has ID of null.

toHaveNumericID

Description

GeoJSON Features have an optional ID member that can be any string or number. This matcher only validates IDs with type number. To check for either string only, use toHaveStringID. To check for either string or number, use toHaveID.

If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.2

An "id" member on a non-feature object gets treated as a foreign member instead of an ID.

This matcher will take an optional argument of either a number, RegExp, or array of any combination of these. If no argument is provided, the matcher will check if the Feature object has an ID member of any value with type number. Otherwise, it will check that the ID member exactly matches either the single input or any of the array values.

Passing a string type to SearchID will not pass the test, even if the ID exactly matches.

Valid GeoJSON Examples

{
    "type": "Feature",
    "id": 123,
    "geometry": {...},
    "properties": {...}
}

Example Matcher Usage

const testFeature = {
    type: 'Feature',
    id: 456,
    geometry: {...},
    properties: {...}
}

const testFeatureNoID = {
    type: 'Feature',
    geometry: {...},
    properties: {...}
}

const testFeatureStringID = {
    type: 'Feature',
    id: 'f1',
    geometry: {...},
    properties: {...}
}

expect(testFeature).toHaveNumericID()
expect(testFeature).toHaveNumericID(456)
expect(testFeature).toHaveNumericID([1, 123, 345, /[a-z]+[0-9]+/])

expect(testFeatureNoID).not.toHaveNumericID()
expect(testFeatureStringID).not.toHaveNumericID()
expect(testFeatureStringID).not.toHaveNumericID('f1')

Passing Tests

Numeric IDs

Input:

  • 1
  • 0
  • 5000
  • -Infinity
  • Infinity
const testFeature = {
    type: 'Feature',
    id: <input>,
    geometry: null,
    properties: null
}
  • Using the matcher without an optional argument
  • Using the matcher with the input argument
  • Using the matcher with the input argument as a single element array
  • Using the matcher with the input argument as a RegExp
  • Using the matcher with the input argument as a single element array as a RegExp

Empty Array for Optional Argument

const testFeature = {
    type: 'Feature',
    id: 719,
    geometry: null,
    properties: null
}

expect(feature).toHaveNumericID([])

Multiple Array Value Checking for Numeric ID

const testFeature = {
    type: 'Feature',
    id: 719,
    geometry: null,
    properties: null
}

Only one element passes:

  • [1, 719, 0, 123]
  • [-5, /7/, 15]
  • [/719/, /123/, /\bString\b/]

More than one element passes:

  • [/72[0-9]/, /\bString\b/, /71[0-9]/, 719]

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects
  • FeatureCollection object
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • '',
  • 'Random Feature',
  • JSON.stringify({
          type: 'Feature',
          geometry: null,
          properties: null
      })
    

Otherwise Valid String IDs Fail

Input:

  • '1'
  • 'Test 123'
  • 'Random ID String'
  • ''
const testFeature = {
    type: 'Feature',
    id: <input>,
    geometry: null,
    properties: null
}
  • Using the matcher without an optional argument
  • With an input argument of an empty array
  • Using the matcher with the input argument
  • Using the matcher with the input argument as a single element array
  • Using the matcher with the input argument as a RegExp
  • Using the matcher with the input argument as a single element array as a RegExp

Invalid Inputs To Optional Argument

Rejects when the optional ID to check is

  • Each of the seven Geometry objects
  • Feature and FeatureCollection object
  • String values: 'Some String', '', '1'
  • undefined, null, false, true
  • { someProp: 'I am not GeoJSON', id: 4 }
  • an empty object: {}
  • NaN

Rejects when ID Does Not Match Optional Input Value

  • String ID that does not match
  • String ID that does match
  • Array of String IDs that do not match
  • RegExp does not match
  • Array of RegExp does not match

Need a checklist for adding new matchers

Component

CONTRIBUTING

Description

Adding matchers has become more complicated now that the matcher and core functionality are split. Additionally, having multiple entry points in the package means multiple environment load scripts to maintain.

A checklist that organizes all of these steps in a logical order would help ensure no steps get missed.

This should extend to the pull request template as well.

Are you able/willing to make the change? (It's ok if you're not!)

Yes

Code of Conduct

toHaveMinGeometryCount

Description

A GeoJSON GeometryCollection contains a "geometries" member with an array of GeoJSON geometry objects.

This matcher uses the toBeGeometryCollection functionality to verify the input is a properly formatted GeometryCollection object, and then determine if it has more than or equal to the MinCount value. It will throw an error for any of the other geometry types as it is a trivial comparison on those.

Omitting MinCount will assume a minimum value of 1. Passing a number less than 0 will throw an error. Decimals will get truncated.

Example Matcher Usage

const testCollection = {
    "type": "GeometryCollection",
    "geometries": [{
        "type": "Point",
        "coordinates": [100.0, 0.0]
    }, {
        "type": "LineString",
        "coordinates": [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }, {
        "type": "Polygon",
        "coordinates": [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ]
    }, {
        "type": "Point",
        "coordinates": [150.0, 73.0]
    }]
}
const emptyCollection = {
    "type": "GeometryCollection",
    "geometries": []
}
const polygon = {
    type: 'Polygon',
    coordinates: [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]
    ]
}

expect(testCollection).toHaveMinGeometryCount()
expect(testCollection).toHaveMinGeometryCount(4)

expect(testCollection).not.toHaveMinGeometryCount(5)
expect(emptyCollection).not.toHaveMinGeometryCount()
expect(polygon).not.toHaveMinGeometryCount(1)

Passing Tests

Good GeometryCollection

  • Known good GeometryCollection with a single geometry
    • .toHaveMinGeometryCount()
    • .toHaveMinGeometryCount(0.9)
    • .toHaveMinGeometryCount(1)
    • .toHaveMinGeometryCount(1.2)
  • Known good GeometryCollection with 4 geometries
    • .toHaveMinGeometryCount()
    • .toHaveMinGeometryCount(3)
    • .toHaveMinGeometryCount(4)
    • .toHaveMinGeometryCount(4.999)
  • Stress test: Known good GeometryCollection with 100 geometries
    • .toHaveMinGeometryCount()
    • .toHaveMinGeometryCount(1)
    • .toHaveMinGeometryCount(50)
    • .toHaveMinGeometryCount(100)
  • Empty Geometry
    • .toHaveMinGeometryCount(0)
    • .toHaveMinGeometryCount(0.1)
    • .toHaveMinGeometryCount(0.9)

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects except GeometryCollection
  • An invalid GeometryCollection
  • Feature and FeatureCollection object
  • undefined, null, false, true, 0, NaN
  • { someProp: 'I am not GeoJSON', id: 4 }
  • {}
  • '',
  • 'Random Feature',
  •   JSON.stringify({
          "type": "GeometryCollection",
          "geometries": [{
              "type": "Point",
              "coordinates": [100.0, 0.0]
          }, {
              "type": "LineString",
              "coordinates": [
                  [101.0, 0.0],
                  [102.0, 1.0]
              ]
          }, {
              "type": "Polygon",
              "coordinates": [
                  [
                      [102.0, 2.0],
                      [103.0, 2.0],
                      [103.0, 3.0],
                      [102.0, 3.0],
                      [102.0, 2.0]
                  ]
              ]
          }, {
              "type": "Point",
              "coordinates": [150.0, 73.0]
          }]
      })

Valid GeometryCollection With Bad MinCount

  • Good GeometryCollection with MinCount as each of the values in "Invalid Inputs To Matcher" except 0

Valid GeometryCollection With Out of Range MinCount

  • Known good GeometryCollection with a single geometry
    • .not.toHaveMinGeometryCount(-1)
    • .not.toHaveMinGeometryCount(2)
  • Known good GeometryCollection with 4 geometries
    • .not.toHaveMinGeometryCount(-10)
    • .not.toHaveMinGeometryCount(-0.001)
    • .not.toHaveMinGeometryCount(5)
  • Stress test: Known good GeometryCollection with 100 geometries
    • .not.toHaveMinGeometryCount(-Infinity)
    • .not.toHaveMinGeometryCount(101)
  • Empty Geometry
    • .not.toHaveMinGeometryCount(-1)
    • .not.toHaveMinGeometryCount(1)
    • .not.toHaveMinGeometryCount()

isValidBoundingBox

Description

This matcher follows the same rules as isValid2DBoundingBox and isValid3DBoundingBox . This particular matcher takes an argument of either a 2D or 3D bounding box and checks its validity.

It takes as an argument of an individual array value (i.e. not the entire coordinates property).

Valid GeoJSON Coordinate Examples

Example of a 3D and 2D bbox on features with a FeatureCollection that also has a bounding box:

{
    "type": "FeatureCollection",
    "bbox":  [-10, -10, -100, 10, 10, 950],
         "features": [
             {
                 "type": "Feature",
                 "bbox": [-10.0, -10.0, -167, 10.0, 10.0, 0],
                 "geometry": {
                     ...
                 }
             },
             {
                 "type": "Feature",
                 "bbox": [-10.0, -10.0, 10.0, 10.0],
                 "geometry": {
                     ...
                 }
             },
             ...
         ]
}

In each case, the matcher should only get passed the value of a single coordinate, not the object itself.

Example Matcher Usage

expect([-10.0, -10.0, 10.0, 10.0]).isValidBoundingBox()
expect([-10, -10, -100, 10, 10, 950]).isValidBoundingBox()

expect([-10.0, -10.0, 10.0, 100.0]).not.isValidBoundingBox() // Longitude out of range
expect([-10, -10, -100, 10, 10, '950']).not.isValidBoundingBox() // Altitude is a string instead of a number

Repeat Tests Contained in Other Files

This matcher works with either 2D or 3D bounding boxes. Therefore, the passing and failing tests for isValid2DBoundingBoxand isValid3DBoundingBox can also include tests for isValidBoundingBox.

Unique Tests

  • Incorrect number of array elements (because 2D and 3D have conflicting tests for the combined matcher)
    • [ ], [ 20 ], [20, 30], [20, 30, 0], [20, 30, 0, 4, 0], [20, 30, 0, 20, 30, 0, 20], [20, 30, 0, 20, 30, 0, 20, 30, 0]
  • Snapshot Testing

toBeGeometryCollection

Description

This is a building block matcher. Geometries are the basis of all objects, and we need a way to reliably test that they have been formatted appropriately.

This matcher focuses only on "GeometryCollection". All geometries within the "geometries" array must be valid GeoJSON geometry objects. Any non-conforming values should fail.

A Geometry object represents points, curves, and surfaces in coordinate space. Every Geometry object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • The value of a Geometry object's "type" member MUST be one of the seven geometry types (per Section 1.4: "Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", and "GeometryCollection").
  • A GeoJSON Geometry object of any type other than "GeometryCollection" has a member with the name "coordinates". The value of the "coordinates" member is an array. The structure of the elements in this array is determined by the type of geometry. GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1

However, the "geometries" property MAY have an empty array ([ ]) as a valid value. It MAY NOT have an array of empty objects as a value.

A GeoJSON object with type "GeometryCollection" is a Geometry object. A GeometryCollection has a member with the name "geometries". The value of "geometries" is an array. Each element of this array is a GeoJSON Geometry object. It is possible for this array to be empty.

Unlike the other geometry types described above, a GeometryCollection can be a heterogeneous composition of smaller Geometry objects. For example, a Geometry object in the shape of a lowercase roman "i" can be composed of one point and one LineString.

GeometryCollections have a different syntax from single type Geometry objects (Point, LineString, and Polygon) and homogeneously typed multipart Geometry objects (MultiPoint, MultiLineString, and MultiPolygon) but have no different semantics. Although a GeometryCollection object has no "coordinates" member, it does have coordinates: the coordinates of all its parts belong to the collection. The "geometries" member of a GeometryCollection describes the parts of this composition. Implementations SHOULD NOT apply any additional semantics to the "geometries" array.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.8

GeometryCollections MAY have as one of their geometries another GeometryCollection. This is recommended against by the spec, but not prohibited.

To maximize interoperability, implementations SHOULD avoid nested GeometryCollections. Furthermore, GeometryCollections composed of a single part or a number of parts of a single type SHOULD be avoided when that single part or a single object of multipart type (MultiPoint, MultiLineString, or MultiPolygon) could be used instead.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.8

The GeometryCollection object should have a "type" and a "geometries" property. It is prohibited from having a "geometry", "properties", or "features" property.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.
https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

Although illogical, it is not prohibited from having a "coordinates" member. If present, it should be treated as a foreign member.

Other foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members should get checked for validity of any type; they may contain anything that is valid JSON.

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Bounding boxes, if present, must validate (see isValidBoundingBox).

A GeoJSON object represents a Geometry, Feature, or collection of Features.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Valid GeoJSON GeometryCollection Examples

{
    "type": "GeometryCollection",
    "geometries": [{
        "type": "Point",
        "coordinates": [100.0, 0.0]
    }, {
        "type": "LineString",
        "coordinates": [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }, {
        "type": "Polygon",
        "coordinates": [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ]
    }, {
        "type": "Point",
        "coordinates": [150.0, 73.0]
    }]
}

An empty coordinates

{
    "type": "GeometryCollection",
    "geometries": [ ]
}

In each case, the matcher should only get passed the object in its entirety, not the individual components, and not as part of another collection object.

Example Matcher Usage

const geometryCollection = {
    type: 'GeometryCollection',
    geometries: [{
        "type": 'Point',
        "coordinates": [100.0, 0.0]
    }, {
        type: 'LineString',
        coordinates: [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }, {
        type: 'Polygon',
        coordinates: [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ]
    }, {
        type: 'Point',
        coordinates: [150.0, 73.0]
    }]
}

expect(geometryCollection).toBeGeometryCollection()

expect(geometryCollection.geometries).not.toBeMultiPolygonGeometry()
expect(geometryCollection.geometries[1]).not.toBeMultiPolygonGeometry()

Passing Tests

This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.

Values in Range

  • Contains 1 or 10 elements of a single type of each of the six geometries
  • Contains 1 element of each of all six geometries
  • Contains 10 elements of each of all six geometries
  • Contains another geometry collection
  • Contains multiple nested geometry collections

Empty Geometries

const testGeometryCollection = {
    type: "GeometryCollection",
    geometries: [ ]
}

Foreign Properties Allowed

  • ID (String and number input: 'Test 1', 1, null
const testGeometryCollection = {
    type: "GeometryCollection",
    id: <input>,
    geometries: ...
}
  • Multiple
const testGeometryCollection = {
    type: "GeometryCollection",
    someRandomProp: true,
    geometries: [{
        type: "Point",
        coordinates: [5, 15]
    }],
    otherProp: [5, 15]
}

Nested GeometryCollections

const testGeometryCollection = {
    type: "GeometryCollection",
    geometries: [{
        type: "GeometryCollection",
        geometries: [{
            type: "GeometryCollection",
            geometries: [{
                type: "Point",
                coordinates: [5, 15]
            }]
        }, {
            type: "Point",
            coordinates: [10, 20]
        }]
    }, {
        type: "Point",
        coordinates: [20, 25]
    }]
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testGeometryCollection = {
    type: 'GeometryCollection',
    geometries: [{
        "type": "Point",
        "coordinates": [0, 0]
    }],
    bbox: <input>
}
  • Illogical
const testGeometryCollection = {
    type: 'GeometryCollection',
    geometries: [{
        "type": "Point",
        "coordinates": [0, 0]
    }],
    bbox: [-30.0, -30.0, -20.0, -20.0]
}
  • Redundant
const testGeometryCollection = {
    type: 'GeometryCollection',
    geometries: [{
        "type": "Point",
        "coordinates": [0, 0]
    }],
    bbox: [0, 0, 0, 0]
}

Coordinates Treated as Foreign Member

  • Input: [0, 0], false, {}, 2, 'Coordinate String'
const testGeometryCollection = {
    type: 'GeometryCollection',
    geometries: [{
        "type": "Point",
        "coordinates": [0, 0]
    }],
    coordinates: <input>
}

Failing Tests

Geometry Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays:
    • [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
    • [1, 1]
  • Strings: '', 'Random Geometry', '[0, 0]'
  • Stringified JSON:
    JSON.stringify({
      "type": "GeometryCollection",
      "geometries": [{
          "type": "Point",
          "coordinates": [100.0, 0.0]
      }]
    })

Invalid Geometries

Should fail when "geometries":

  • Is a single bad element of each geometry
  • Is a good geometry of each element mixed with a bad geometry

Type Value Incorrect

Input:

  • Each of the values from "Geometry Input Not an Object", plus
  • 'Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', and 'MultiPolygon', or
  • 'GEOMETRYCOLLECTION', 'geometrycollection'
{
    type: <input>,
    geometries: [{
        "type": "Point",
        "coordinates": [-180, 90, 2000]
    }]
}

Contains Prohibited Properties

Geometry

const testGeometryCollection = {
    type: "GeometryCollection",
    geometries: [{
        "type": "Point",
        "coordinates": [180, -90, 2000]
    }],
    geometry: {
        type: "Point",
        coordinates: [102.0, 0.5]
    }
}

Properties

const testGeometryCollection = {
    type: "GeometryCollection",
    geometries: [{
        "type": "Point",
        "coordinates": [0, 0, -2000]
    }],
    properties: {
        prop1: true
    }
}

Features

const testGeometryCollection = {
    type: "GeometryCollection",
    geometries: [{
        "type": "Point",
        "coordinates": [0, 0]
    }],
    features: [{
        type: "Feature",
        geometry: {
            type: "Point",
            coordinates: [102.0, 0.5]
            },
        properties: {
           prop0: "value0"
        }
    }]
}

Missing Required Properties

  • No Type
const testGeometryCollection = {
    geometries: [{
        "type": "Point",
        "coordinates": [0, 0]
    }]
}
  • No Geometries
const testGeometryCollection = {
    type: "GeometryCollection"
}

Geometries is an Array or Object of Null Arrays/Objects

  • Inputs:
    • [[]]
    • [[], []]
    • [{}, {}]
    • {}
const testGeometryCollection = {
    type: "GeometryCollection",
    geometries: <input>
}

Invalid Bounding Box

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testGeometryCollection = {
    type: 'GeometryCollection',
    geometries: [
                    {
                        type: 'Point',
                        coordinates: [180, -90, 2000]
                    }
                ],
    bbox: <input>
}

toBePolygonGeometry

Description

This is a building block matcher. Geometries are the basis of all objects, and we need a way to reliably test that they have been formatted appropriately.

This matcher focuses only on "Polygon". All coordinates must be valid coordinates per the WGS-84 standard. Any non-conforming values should fail. However, the "coordinates" property MAY have an empty array ([ ]) as a valid value. It MAY NOT have an array of empty arrays as a value ([ [ ], [ ] ]).

A Geometry object represents points, curves, and surfaces in coordinate space. Every Geometry object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • The value of a Geometry object's "type" member MUST be one of the seven geometry types (per Section 1.4: "Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", and "GeometryCollection").
  • A GeoJSON Geometry object of any type other than "GeometryCollection" has a member with the name "coordinates". The value of the "coordinates" member is an array. The structure of the elements in this array is determined by the type of geometry. GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1

Polygons are similar to MultiLineStrings, but each coordinate array must have four or more positions with the first equaling the last.

To specify a constraint specific to Polygons, it is useful to introduce the concept of a linear ring:

  • A linear ring is a closed LineString with four or more positions.
  • The first and last positions are equivalent, and they MUST contain identical values; their representation SHOULD also be identical.
  • A linear ring is the boundary of a surface or the boundary of a hole in a surface.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6

This matcher will make no assumptions about winding order. A planned future matcher will check that. Per the standard, as long as it meets the above criteria, it is a valid polygon object.

  • A linear ring is the boundary of a surface or the boundary of a hole in a surface.
  • A linear ring MUST follow the right-hand rule with respect to the area it bounds, i.e., exterior rings are counterclockwise, and holes are clockwise.

Note: the [GJ2008] specification did not discuss linear ring winding order. For backwards compatibility, parsers SHOULD NOT reject Polygons that do not follow the right-hand rule.

Though a linear ring is not explicitly represented as a GeoJSON geometry type, it leads to a canonical formulation of the Polygon geometry type definition as follows:

  • For type "Polygon", the "coordinates" member MUST be an array of linear ring coordinate arrays.

  • For Polygons with more than one of these rings, the first MUST be the exterior ring, and any others MUST be interior rings. The exterior ring bounds the surface, and the interior rings (if present) bound holes within the surface.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6

The geometry object should have a "type" and a "coordinates" property. It is prohibited from having a "geometry", "properties", or "features" property.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.
https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

Although illogical, it is NOT PROHIBITED from having a "geometries" property, although if present it would be considered a foreign member.

Other foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members should get checked for validity of any type; they may contain anything that is valid JSON.

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Bounding boxes, if present, must validate (see isValidBoundingBox).

A GeoJSON object represents a Geometry, Feature, or collection of Features.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Valid GeoJSON Polygon Examples

No Holes:

    {
        "type": "Polygon",
        "coordinates": [
            [
                [100.0, 0.0],
                [101.0, 0.0],
                [101.0, 1.0],
                [100.0, 1.0],
                [100.0, 0.0]
            ]
        ]
    }

With Holes:

    {
        "type": "Polygon",
        "coordinates": [
            [
                [100.0, 0.0],
                [101.0, 0.0],
                [101.0, 1.0],
                [100.0, 1.0],
                [100.0, 0.0]
            ],
            [
                [100.8, 0.8],
                [100.8, 0.2],
                [100.2, 0.2],
                [100.2, 0.8],
                [100.8, 0.8]
            ]
        ]
    }

An empty coordinates

{
    "type": "Polygon",
    "coordinates": [ ]
}

In each case, the matcher should only get passed the object in its entirety, not the individual components, and not as part of another collection object.

Example Matcher Usage

const polygon = {
    "type": "Polygon",
    "coordinates": [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]
    ]
}
const polygonWithoutEnoughPoints = {
    "type": "Polygon",
    "coordinates": [
        [
            [100.0, 0.0],
            [101.0, 0.0]
        ]
    ]
}
const multiLineString = {
    type: "MultiLineString",
    coordinates: [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]
    ]
}

expect(polygon).toBePolygonGeometry()

expect(polygonWithoutEnoughPoints).toBePolygonGeometry()
expect(multiLineString).not.toBePolygonGeometry()
expect(polygon.coordinates).not.toBePolygonGeometry()

Passing Tests

This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.

Values in Range

  • Counterclockwise wound
    • 2D points, 3D points, Mixed 2D and 3D
    • Both without and with holes
  • Clockwise wound
    • 2D points, 3D points, Mixed 2D and 3D
    • Both without and with holes
  • Spanning the antimeridian
    • Note: this is an assumption inferred from proximity. No way to tell without a bounding box on a polygon feature
  • Redundant
    • Exterior ring all the same point
    • Interior rings all the same point
    • Interior rings outside exterior rings
  • Stressed: A 30 element polygon, each element containing 30 coordinates
const testPolygon = {
    type: "Polygon",
    coordinates: [<input>]
}
  • Empty Coordinate
const testPolygon = {
    type: "Polygon",
    coordinates: [ ]
}

Foreign Properties Allowed

  • ID (String and number input: 'Test 1', 1
const testPolygon = {
    type: "Polygon",
    id: <input>,
    coordinates: [[[25, 90], [-180, 0]]]
}
  • Non alphanumeric ID
    ID Normally needs to be a letter or number, but because it is a foreign member on a geometry object, the GeoJSON standard takes no consideration about it.
const testPolygon1 = {
    type: "Polygon",
    id: null,
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
}
  • Geometries
const testPolygon2 = {
    type: "Polygon",
    geometries: testPolygon1,
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
}
  • Multiple
const testPolygon3 = {
    type: "Polygon",
    someRandomProp: true,
    geometries: testPolygon2,
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testPolygon = {
    type: 'Polygon',
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
    bbox: <input>
}
  • Illogical
const testPolygon = {
    type: 'Polygon',
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
    bbox: [-30.0, -30.0, -20.0, -20.0]
}
  • Redundant
const testPolygon = {
    type: 'Polygon',
    coordinates: [[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]],
    bbox: [0, 0, 0, 0]
}

Failing Tests

Geometry Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays:
    • [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
    • [1, 1]
    • [ ]
  • Strings: '', 'Random Geometry', '[0, 0]', '[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]'
  • Stringified JSON:
    JSON.stringify({
      type: "Polygon",
      coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
    })

Invalid Coordinates

  • Each of the values from "Geometry Input Not an Object":
const testPolygon= {
    type: "Polygon",
    coordinates: [[[0, 0], [0, 1], [1, 1], <input>, [0, 0]]]
}

Coordinates Out of Range

This only tests a handful of invalid coordinates to verify behavior. Detailed coordinate validation occurs in isValidCoordinate

Input:

  • [[181, 0], [0, 1], [1, 1], [1, 0], [181, 0]]
  • [[0, 0], [0, 91], [1, 1], [1, 0], [0, 0]]
  • [[0, 0], [0, 1], [-181, 1], [1, 0], [0, 0]]
  • [[0, -181], [0, 1], [1, 1], [1, 0], [0, -181]]
  • [[0, 0, 0, 0]]
const testPolygon= {
    type: "Polygon",
    coordinates: [<input>]
}

Not Enough Coordinates

  • The each polygon linestring array in coordinates must have four or more positions.

Input:

  • [[[0, 0]]]
  • [[[0, 0], [1, 1]]]
  • [[[0, 0], [1, 1]], [[1, 0]]]
const testPolygon= {
    type: "Polygon",
    coordinates: <input>
}

Final Coordinate does not Match First Coordinate

Input:

  • [[0, 0], [0, 1], [1, 1], [1, 0]]
  • [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], [[0, 0], [0, 1], [1, 1], [1, 0]]
  • [[0, 0], [0, 1], [1, 1], [1, 0], [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
const testPolygon= {
    type: "Polygon",
    coordinates: [<input>]
}

Type Value Incorrect

Input:

  • Each of the values from "Geometry Input Not an Object", plus
  • 'Point', 'MultiPoint', 'LineString', 'MultiLineString', 'MultiPolygon', and 'GeometryCollection', or
  • 'POLYGON', 'polygon'

Input:

  • [[[0, 0]]]
  • [[[0, 0], [1, 1]]]
  • [[[0, 0], [1, 1]], [[1, 0]]]
const testPolygon= {
    type: "Polygon",
    coordinates: <input>
}

Contains Prohibited Properties

Geometry

const testPolygon = {
    type: "Polygon",
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
    geometry: {
        type: "Point",
        coordinates: [102.0, 0.5]
    }
}

Properties

const testPolygon = {
    type: "Polygon",
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
    properties: {
        prop1: true
    }
}

Features

const testPolygon = {
    type: "Polygon",
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
    features: [{
        type: "Feature",
        geometry: {
            type: "Point",
            coordinates: [102.0, 0.5]
            },
        properties: {
           prop0: "value0"
        }
    }]
}

Missing Required Properties

  • No Type
const testPolygon = {
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
}
  • No Coordinates
const testPolygon = {
    type: "Polygon"
}

Coordinates is an Array of Null Arrays

  • Test for 1x, 2x, 3x
  • No Type
const testPolygon = {
    type: "Polygon",
    coordinates: [[<arrays>]]
}

Invalid Bounding Box

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testPolygon = {
    type: 'Polygon',
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
    bbox: <input>
}

toBePolygonWithHole

Description

GeoJSON Polygon geometries support multiple linear rings in their coordinates array. Each ring after the first represents a hole.

  • A linear ring is the boundary of a surface or the boundary of a hole in a surface.
  • A linear ring MUST follow the right-hand rule with respect to the area it bounds, i.e., exterior rings are counterclockwise, and holes are clockwise.

Note: the [GJ2008] specification did not discuss linear ring winding order. For backwards compatibility, parsers SHOULD NOT reject Polygons that do not follow the right-hand rule.

Though a linear ring is not explicitly represented as a GeoJSON geometry type, it leads to a canonical formulation of the Polygon geometry type definition as follows:

  • For type "Polygon", the "coordinates" member MUST be an array of linear ring coordinate arrays.

For Polygons with more than one of these rings, the first MUST be the exterior ring, and any others MUST be interior rings. The exterior ring bounds the surface, and the interior rings (if present) bound holes within the surface.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6

This matcher will make no assumptions about winding order. A planned future matcher will check that. Per the standard, as long as it meets the above criteria, it is a valid polygon object. It will validate the geometry using the toBePolygonGeometry functionality.

Valid GeoJSON Examples

{
    "type": "Polygon",
    "coordinates": [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ],
        [
            [100.8, 0.8],
            [100.8, 0.2],
            [100.2, 0.2],
            [100.2, 0.8],
            [100.8, 0.8]
        ]
    ]
}

Example Matcher Usage

const polygon1 = {
    type: 'Polygon',
    coordinates: [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ],
        [
            [100.8, 0.8],
            [100.8, 0.2],
            [100.2, 0.2],
            [100.2, 0.8],
            [100.8, 0.8]
        ]
    ]
}
const polygon2 = {
    type: 'Polygon',
    coordinates: [
        [
            [120.0, 0.0],
            [121.0, 0.0],
            [121.0, 1.0],
            [120.0, 1.0],
            [120.0, 0.0]
        ]
    ]
}
const point = {
    type: 'Polygon',
    coordinates: [
        [
            [120.0, 0.0],
            [121.0, 0.0],
            [121.0, 1.0],
            [120.0, 1.0],
            [120.0, 0.0]
        ]
    ]
}

expect(polygon1).toBePolygonWithHole()

expect(polygon2).not.toBePolygonWithHole()
expect(point).not.toBePolygonWithHole()

Passing Tests

Good Polygons

Test each for clockwise and counterclockwise:

  • Known good polygon with a single hole
  • Known good polygon with 2 holes
  • Known good polygon with 30 holes

Illogical Holes

  • Hole has 0 area
  • Hole is outside geometry
  • Hole is wound counterclockwise

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects except polygon
  • An invalid polygon
  • Feature and FeatureCollection object
  • undefined, null, false, true, 0, NaN
  • { someProp: 'I am not GeoJSON', id: 4 }
  • {}
  • '',
  • 'Random Feature',
  •   JSON.stringify({
          type: 'Polygon',
          coordinates: [
              [
                  [100.0, 0.0],
                  [101.0, 0.0],
                  [101.0, 1.0],
                  [100.0, 1.0],
                  [100.0, 0.0]
              ],
              [
                  [100.8, 0.8],
                  [100.8, 0.2],
                  [100.2, 0.2],
                  [100.2, 0.8],
                  [100.8, 0.8]
              ]
          ]
      })

Non-Passing Polygons

  • Polygon with bad geometry
  • Polygon with no hole

toContainStringIDs

Description

GeoJSON Features have an optional ID member that can be any string or number. This matcher verifies that all Features within a FeatureCollection contain IDs and that they are all type string.

If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.2

An "id" member on a non-feature object gets treated as a foreign member instead of an ID. Therefore, this matcher rejects non-FeatureCollection objects.

This test will fail if any of the IDs have a type number, do not have an ID, or if the "features" member is an empty array.

Example Matcher Usage

const goodTtestCollection = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: "Test 1"
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: "Test 2"
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: "Test 3"
    }]
}
const badTestCollection1 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: "Test 1"
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 2
    }]
}

const badTestCollection2 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: "Test 1"
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
    }]
}

expect(goodTtestCollection).toContainStringIDs()

expect(badTestCollection1).not.toContainStringIDs()
expect(badTestCollection2).not.toContainStringIDs()
expect(goodTtestCollection.features[0]).not.toContainStringIDs()

Passing Tests

Unique String IDs

  • FeatureCollection with Single Feature with ID
  • FeatureCollection with 5 Features and unique IDs
  • Stress Test: FeatureCollection with 100 Features and unique IDs

Identical String IDs

  • FeatureCollection with 5 Features and some repeated IDs
  • FeatureCollection with 5 Features and all repeated IDs

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects
  • FeatureCollection object
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • '',
  • 'Random Feature',
  • JSON.stringify({
          type: 'FeatureCollection',
          features: []
      })

Only Some Have String IDs, the Rest Null or Not Mentioned

  • FeatureCollection with 5 Features, 1 has no "id" member
  • FeatureCollection with 5 Features, 1 has an "id" member with null

FeatureCollection with Empty Features Array

Should fail because there are no features to check IDs on.

FeatureCollection with No ID

Should fail with a single feature that has no ID.

All Have Number IDs

  • FeatureCollection with Single Feature and ID
  • FeatureCollection with 5 Features and unique IDs
  • FeatureCollection with 5 Features and repeated IDs

Only Some Have Number IDs, the Rest String ID, Null, or Not Mentioned

  • FeatureCollection with 5 Features
  • FeatureCollection with 100 Features

toBeMultiLineStringGeometry

Description

This is a building block matcher. Geometries are the basis of all objects, and we need a way to reliably test that they have been formatted appropriately.

This matcher focuses only on "MultiLineString". All coordinates must be valid coordinates per the WGS-84 standard. Any non-conforming values should fail. However, the "coordinates" property MAY have an empty array ([ ]) as a valid value. It MAY NOT have an array of empty arrays as a value ([ [ ], [ ] ]).

A Geometry object represents points, curves, and surfaces in coordinate space. Every Geometry object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • The value of a Geometry object's "type" member MUST be one of the seven geometry types (per Section 1.4: "Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", and "GeometryCollection").
  • A GeoJSON Geometry object of any type other than "GeometryCollection" has a member with the name "coordinates". The value of the "coordinates" member is an array. The structure of the elements in this array is determined by the type of geometry. GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1

For type "MultiLineString", the "coordinates" member is an array of LineString coordinate arrays.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.5

The geometry object should have a "type" and a "coordinates" property. It is prohibited from having a "geometry", "properties", or "features" property.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.
https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

Although illogical, it is NOT PROHIBITED from having a "geometries" property, although if present it would be considered a foreign member.

Other foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members should get checked for validity of any type; they may contain anything that is valid JSON.

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Bounding boxes, if present, must validate (see isValidBoundingBox).

A GeoJSON object represents a Geometry, Feature, or collection of Features.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Valid GeoJSON MultiLineString Examples

Multiple Points:

    {
        "type": "MultiLineString",
        "coordinates": [
            [
                [101.0, 1.0]
            ],
            [
                [102.0, 2.0],
                [103.0, 3.0]
            ]
        ]
    }

An empty coordinates

{
    "type": "MultiLineString",
    "coordinates": [ ]
}

Contained as an object within a geometries array on a GeometryCollection:

{
    "type": "GeometryCollection",
    "geometries": [{
        "type": "MultiLineString",
        "coordinates": [
            [
                [100.0, 0.0, 0],
                [101.0, 1.0, 0]
            ],
            [
                [102.0, 2.0, 0],
                [103.0, 3.0, 0]
            ]
        ]
    }, {
        "type": "MultiPoint",
        "coordinates": [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }]
}

In each case, the matcher should only get passed the object in its entirety, not the individual components, and not as part of another collection object.

Example Matcher Usage

const multiLineString = {
    "type": "MultiLineString",
    "coordinates": [
        [
            [100.0, 0.0],
            [101.0, 1.0]
        ],
        [
            [102.0, 2.0],
            [103.0, 3.0]
        ]
    ]
}
const multiPoint = {
    type: "MultiPoint",
    coordinates: [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}

expect(multiLineString).toBeMultiLineStringGeometry()

expect(multiPoint).not.toBeMultiLineStringGeometry()
expect(multiLineString.coordinates).not.toBeMultiLineStringGeometry()

Passing Tests

This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.

Values in Range

  • 2D: [[[0, 1], [0, 2]], [[1, 0], [2, 0], [3, 0]]]

  • 3D: [[[2, 20, 0], [4, 10, 0]], [[3, 0.0, 0], [6, -10, 0], [9, -20, 0]]]

  • Mixed: [[[100.0, 0.0], [90, 0.0, 0]], [[100.0, 0.0, 0], [110, 5], [100.0, 11.33, 259]]]

  • Single LineString: [[[180.0, 40.0], [180.0, 50.0], [170.0, 50.0], [170.0, 40.0], [180.0, 40.0]]]

  • Antimeridian: [[[175, 0], [-175, 0]], [[-175, 0], [175, 0]]]

    • Note that both of these are only implied to cross the antimeridian due to proximity. The actual standard assumes these would span 350 degrees. Either way, they are valid coordinate arrays.
  • Repeated and Redundant: [[[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]]]

  • Stressed: A 30 element array, each element being an array of 30 coordinates

    const testMultiLineString = {
        type: "MultiLineString",
        coordinates: <input>
    }
  • Empty Coordinate

const testMultiLineString = {
    type: "MultiLineString",
    coordinates: [ ]
}

Foreign Properties Allowed

  • ID (String and number input: 'Test 1', 1
const testMultiLineString = {
    type: "MultiLineString",
    id: <input>,
    coordinates: [[[25, 90], [-180, 0]]]
}
  • Non alphanumeric ID
    ID Normally needs to be a letter or number, but because it is a foreign member on a geometry object, the GeoJSON standard takes no consideration about it.
const testMultiLineString1 = {
    type: "MultiLineString",
    id: null,
    coordinates: [[[25, 90], [-180, 0]]]
}
  • Geometries
const testMultiLineString2 = {
    type: "MultiLineString",
    geometries: testMultiLineString1,
    coordinates: [[[-100.0, -15.0, 2000], [0, 0]]]
}
  • Multiple
const testMultiLineString3 = {
    type: "MultiLineString",
    someRandomProp: true,
    geometries: testMultiLineString2,
    coordinates: [[[180, 10.2, -125], [-180, 90, 35000]]]
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testMultiLineString = {
    type: 'MultiLineString',
    coordinates: [[[0, 0], [-5, -5]]],
    bbox: <input>
}
  • Illogical
const testMultiLineString = {
    type: 'MultiLineString',
    coordinates: [[[0, 0], [-5, -5]]],
    bbox: [-30.0, -30.0, -20.0, -20.0]
}
  • Redundant
const testMultiLineString = {
    type: 'MultiLineString',
    coordinates: [[[0, 0], [-5, -5]]],
    bbox: [0, 0, 0, 0]
}

Failing Tests

Geometry Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays:
    • [[[180, 10.2, -125], [-180, 90, 35000]]], [ ]
  • Strings: '', 'Random Geometry', '[0, 0]', '[[[0, 0], [0, 0]]]'
  • Stringified JSON:
    JSON.stringify({
      type: "MultiLineString",
      coordinates: [[[25, 90], [2, 2]]]
    })

Invalid Coordinates

  • Each of the values from "Geometry Input Not an Object":
const testMultiLineString= {
    type: "MultiLineString",
    coordinates: [[[0, 0], <input>]]
}

Coordinates Out of Range

This only tests a handful of invalid coordinates to verify behavior. Detailed coordinate validation occurs in isValidCoordinate

Input:

  • [[[181, 0], [0, 1], [1, 1], [1, 0], [181, 0]]]
  • [[[0, 0], [0, 91], [1, 1], [1, 0], [0, 0]]]
  • [[[0, 0], [0, 1], [-181, 1], [1, 0], [0, 0]]]
  • [[[0, -181], [0, 1], [1, 1], [1, 0], [0, -181]]]
  • [[[0, 0, 0, 0]]]
const testMultiLineString= {
    type: "MultiLineString",
    coordinates: <input>
}

Single Coordinates

  • The each polygon linestring array in coordinates must have two or more positions.
const testMultiLineString= {
    type: "MultiLineString",
    coordinates: [[[0, 0]]]
}

Type Value Incorrect

Input:

  • Each of the values from "Geometry Input Not an Object", plus
  • 'Point', 'MultiPoint', 'LineString', 'Polygon', 'MultiPolygon', and 'GeometryCollection', or
  • 'MULTILINESTRING', 'multilinestring'
const testMultiLineString = {
    type: <input>,
    coordinates: [[[0, 0], [1, 1, 0]]]
}

Contains Prohibited Properties

Geometry

const testMultiLineString = {
    type: "MultiLineString",
    coordinates: [[[0, 0], [1, 1, 0]]],
    geometry: {
        type: "Point",
        coordinates: [102.0, 0.5]
    }
}

Properties

const testMultiLineString = {
    type: "MultiLineString",
    coordinates: [[[0, 0], [1, 1, 0]]],
    properties: {
        prop1: true
    }
}

Features

const testPolygon = {
    type: "Polygon",
    coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
    features: [{
        type: "Feature",
        geometry: {
            type: "Point",
            coordinates: [102.0, 0.5]
            },
        properties: {
           prop0: "value0"
        }
    }]
}

Missing Required Properties

  • No Type
const testMultiLineString = {
    coordinates: [[[0, 0], [1, 1, 0]]]
}
  • No Coordinates
const testMultiLineString = {
    type: "MultiLineString"
}

Coordinates is an Array of Null Arrays

  • Test for 1x, 2x, 3x
  • No Type
const testMultiLineString = {
    type: "MultiLineString",
    coordinates: [[<arrays>]]
}

Invalid Bounding Box

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testPoint = {
    type: 'MultiLineString',
    coordinates: [[[0, 0], [-5, -5]]],
    bbox: <input>
}

toHaveStringID

Description

GeoJSON Features have an optional ID member that can be any string or number. This matcher only validates IDs with type string. To check for either number only, use toHaveNumericID. To check for either string or number, use toHaveID.

If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.2

An "id" member on a non-feature object gets treated as a foreign member instead of an ID.

This matcher will take an optional argument of either a string, RegExp, or array of any combination of these. If no argument is provided, the matcher will check if the Feature object has an ID member of any value with type string. Otherwise, it will check that the ID member exactly matches either the single input or any of the array values.

Passing a number type to this optional argument will not pass the test, even if the id exactly matches.

Valid GeoJSON Examples

{
    "type": "Feature",
    "id": "f1",
    "geometry": {...},
    "properties": {...}
}

Example Matcher Usage

const testFeature = {
    type: 'Feature',
    id: 'f1',
    geometry: {...},
    properties: {...}
}

const testFeatureNoID = {
    type: 'Feature',
    geometry: {...},
    properties: {...}
}

const testFeatureNumID = {
    type: 'Feature',
    id: 2,
    geometry: {...},
    properties: {...}
}

expect(testFeature).toHaveStringID()
expect(testFeature).toHaveStringID('f1')
expect(testFeature).toHaveStringID(['1', 'F', 'F12', /[a-z]+[0-9]+/])

expect(testFeatureNoID).not.toHaveStringID()
expect(testFeatureNumID).not.toHaveStringID()
expect(testFeatureNumID).not.toHaveStringID(2)

Passing Tests

String IDs

Input:

  • '1'
  • 'Test 123'
  • 'Random ID String'
  • Empty string: ''
const testFeature = {
    type: 'Feature',
    id: <input>,
    geometry: null,
    properties: null
}
  • Using the matcher without an optional argument
  • Using the matcher with the input argument
  • Using the matcher with the input argument as a single element array
  • Using the matcher with the input argument as a RegExp
  • Using the matcher with the input argument as a single element array as a RegExp

Empty Array for Optional Argument

const testFeature = {
    type: 'Feature',
    id: 'Test ID',
    geometry: null,
    properties: null
}

expect(feature).toHaveStringID([])

Multiple Array Value Checking for String ID

const testFeature = {
    type: 'Feature',
    id: 'Some String',
    geometry: null,
    properties: null
}

Only one element passes:

  • ['1', '#719', 'TestID', 'Some String']
  • ['A String ID', /Some/, '2']
  • [/SomeString/, /123/, /\bString\b/]

More than one element passes:

  • [/72[0-9]/, /\bString\b/, /71[0-9]/, /Some/, 'Some String']

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects
  • FeatureCollection object
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • '',
  • 'Random Feature',
  • JSON.stringify({
          type: 'Feature',
          geometry: null,
          properties: null
      })
    

Otherwise Valid Numeric IDs Fail

Input:

  • 0
  • -200
  • 200
  • Infinity
  • -Infinity
const testFeature = {
    type: 'Feature',
    id: <input>,
    geometry: null,
    properties: null
}
  • Using the matcher without an optional argument
  • With an input argument of an empty array
  • Using the matcher with the input argument
  • Using the matcher with the input argument as a single element array
  • Using the matcher with the input argument as a RegExp
  • Using the matcher with the input argument as a single element array as a RegExp

Invalid Inputs To Optional Argument

Rejects when the optional ID to check is

  • Each of the seven Geometry objects
  • Feature and FeatureCollection object
  • undefined, null, false, true
  • { someProp: 'I am not GeoJSON', id: 4 }
  • an empty object: {}
  • NaN

Rejects when ID Does Not Match Optional Input Value

  • String ID that does not match
  • Array of String IDs that do not match
  • RegExp does not match
  • Array of RegExp does not match
  • Numeric ID that does match

[Docs Request]: Identify what the minimum Jest version is to run

Component

Package Peer Dependency

Description

At a minimum, it looks like Jest v24 was the first version that included "setupFilesAfterEnv" in the configuration file. jest-geojson uses this in its own testing scripts and recommends others do as well, so v24 will likely be the minimum peer dependency version.

However, jest-extended uses v27.2.5 as their min. Looking at the commit history, it might be related to the utility functions for formatting matcher responses, but I am not positive.

This will require experimentation to identify what the minimum supported version is. Once identified:

  • Update peer dependency in package.json
  • Update README
  • Identify in CONTRIBUTING why the dependency exists

Are you able/willing to make the change? (It's ok if you're not!)

Yes

Code of Conduct

toBeFeature

Description

This is a building block matcher. Features contain Geometry objects and additional properties. We need a way to reliably test that they have been formatted appropriately.

This matcher tests that an object is a valid feature with any geometry type. Any non-conforming values should fail.

  • It must have a "type" member equal to 'Feature'
  • It must have a "geometry" member with either an object of one of the valid seven geometry types, or the value null
  • It must have a "properties" member, but this may be null or an empty object ({}).
  • If it has an optional "id" member, it must be either a number (except NaN) or string.

A Feature object represents a spatially bounded thing. Every Feature object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • A Feature object has a "type" member with the value "Feature".
  • A Feature object has a member with the name "geometry". The value of the geometry member SHALL be either a Geometry object as defined above or, in the case that the Feature is unlocated, a JSON null value.
  • If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.
  • A Feature object has a member with the name "properties". The value of the properties member is an object (any JSON object or a JSON null value).

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.2

Feature objects are prohibited from having a "coordinates", "geometries", or "features" property.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

Other foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members should get checked for validity of any type; they may contain anything that is valid JSON.

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Valid GeoJSON Feature Examples

{
    "type": "Feature",
    "bbox": [-10.0, -10.0, 10.0, 10.0],
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [-10.0, -10.0],
                [10.0, -10.0],
                [10.0, 10.0],
                [-10.0, -10.0]
            ]
        ]
    },
    "properties": {
        "prop0": "value0",
        "prop1": {
            "this": "that"
        }
    }
}

An empty geometry and properties

{
    "type": "Feature",
    "geometry": null,
    "properties": null
}
{
    "type": "Feature",
    "geometry": null,
    "properties": {}
}

In each case, the matcher should only get passed the object in its entirety, not the individual components, and not as part of another collection object.

Example Matcher Usage

This matcher will have an optional argument (geometryType) that can check the geometry is a specific type:

expect(testFeature).toBeFeature(geometryType)

Omitting the type will check for any valid geometry.

const testFeature = {
    "type": "Feature",
    "bbox": [-10.0, -10.0, 10.0, 10.0],
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [-10.0, -10.0],
                [10.0, -10.0],
                [10.0, 10.0],
                [-10.0, -10.0]
            ]
        ]
    },
    "properties": {
        "prop0": "value0",
        "prop1": {
            "this": "that"
        }
    }
}
const multiPoint = {
    type: "MultiPoint",
    coordinates: [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}

expect(testFeature).toBeFeature()
expect(testFeature).toBeFeature('Polygon')

expect(multiPoint).not.toBeFeature()
expect(testFeature).not.toBeFeature('LineString')
expect(testFeature.geometry).not.toBeFeature('Polygon')

Passing Tests

This matcher should use core functions to test coordinate validity.

Values in Range

  • A Feature containing each of the seven geometry types
    • With no optional argument (i.e. .toBeFeature())
    • Optional argument specifying the type (i.e. .toBeFeature('Point'))

Geometry May Be a Null Value

const testFeature = {
    type: 'Feature',
    geometry: null,
    properties: {
        prop1: 'Some Prop'
    }
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testFeature = {
    type: 'Feature',
    bbox: <input>,
    geometry: {
        "type": "Polygon",
        "coordinates": [
            [
                [-10.0, -10.0],
                [10.0, -10.0],
                [10.0, 10.0],
                [-10.0, -10.0]
            ]
        ]
    },
    properties: null
}
  • Illogical
const testFeature = {
    type: 'Feature',
    bbox: [-30.0, -30.0, -20.0, -20.0],
    geometry: {
        "type": "Polygon",
        "coordinates": [
            [
                [-10.0, -10.0],
                [10.0, -10.0],
                [10.0, 10.0],
                [-10.0, -10.0]
            ]
        ]
    },
    properties: null
}
  • Redundant
const testFeature = {
    type: 'Feature',
    bbox: [0, 0, 0, 0],
    geometry: null,
    properties: null
}

Properties May Be a Null Value or Empty Object

const testFeature = {
    type: 'Feature',
    geometry: null,
    properties: null
}
const testFeature = {
    type: 'Feature',
    geometry: null,
    properties: {}
}

Optional ID Must Be String or Number

  • Strings: 'ABCD', 'Test 1', '1', '', '[[[180, 10.2, -125], [-180, 90, 35000]]], [{}]'
  • Numbers: 0, 200, -200, Infinity, -Infinity,
const testFeature = {
    type: 'Feature',
    id: <input>,
    geometry: null,
    properties: null
}

Foreign Properties Allowed

  • Single
const testFeature = {
    type: 'Feature',
    geometry: null,
    properties: null,
    foreign: true
}
  • Multiple
const testFeature = {
    type: 'Feature',
    id: '#1',
    geometry: null,
    properties: null,
    foreign1: true,
    foreign2: 33
}
  • Foreign property that would normally be a valid GeoJSON object
const multiPoint = {
    type: 'MultiPoint',
    coordinates: [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}

const testFeature = {
    type: 'Feature',
    geometry: multiPoint,
    properties: null,
    geometryDuplicate: multiPoint,
    Geometry: [] // Note captitalized 'G'
}

Failing Tests

Feature Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays: [25, 35, 45000], [ ]
  • Strings: '', 'Random Feature'
  • Stringified JSON:
    JSON.stringify({
      type: 'Feature',
      geometry: null,
      properties: null
    })

Invalid Geometry

  • A Feature containing an invalid version of each of the seven geometry types
    • With no optional argument (i.e. .toBeFeature())
    • Optional argument specifying the type (i.e. .toBeFeature('Point'))

Type Value Incorrect

Input:

  • Each of the values from "Feature Input Not an Object", plus
  • 'Point', 'MultiPoint', 'LineString', 'MultiLineString' 'Polygon', 'MultiPolygon', and 'GeometryCollection', or
  • 'FEATURE', 'feature'
const testFeature = {
    type: <input>,
    geometry: null,
    properties: null
}

Invalid Bounding Box

Input:

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testFeature = {
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null,
    bbox: <input>
}

Non-alphanumeric ID

Input:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: NaN
  • Arrays: [25, 35, 45000], [ ]
  • Objects: {prop: 1}, { }
const testFeature = {
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null,
    id: <input>
}

Contains Prohibited Properties

Coordinates

const testFeature = {
    type: 'Feature',
    coordinates: [[0, 0], [1, 1, 0]],
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null
}

Geometries

const testFeature = {
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null,
    geometries: [
        {
            type: 'Point',
            coordinates: [102.0, 0.5]
        }, {
            type: 'Point',
            coordinates: [122.0, -10.25]
        }
    ]
}

Features

const testFeature = {
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null,
    features: [{
        type: 'Feature',
        geometry: {
            type: 'Point',
            coordinates: [0, 0]
            },
        properties: {
           prop0: 'value0'
        }
    }],
    id: 33
}

Missing Required Properties

  • No Type
const testFeature = {
    properties: null,
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    }
}
  • No Geometry
const testFeature = {
    type: 'Feature',
    properties: null
}
  • No Properties
const testFeature = {
    type: 'Feature',
    geometry: null
}

Geometry contained in an array

  • Each of the values from "Feature Input Not an Object" except null, plus
  • Single Element
const testFeature = {
    type: 'Feature',
    properties: null,
    geometry: [{
        type: 'Point',
        coordinates: [102.0, 0.5]
    }]
}
  • Multiple Elements
const testFeature = {
    type: 'Feature',
    properties: null,
    geometry: [{
        type: 'Point',
        coordinates: [102.0, 0.5]
    }, {
        type: 'Point',
        coordinates: [122.0, -10.25]
    }]
}

If Not Null, Properties Must be an Object

  • Each of the values from "Feature Input Not an Object" except null

isValid3DCoordinate

Description

For testing 2D coordinates, see isValid2DCoordinate. For testing either, see isValidCoordinate.

This is one of the first building block matchers needed. Coordinates are the basis of all objects, and we need a way to reliably test that they have been formatted appropriately.

A GeoJSON Geometry object of any type other than "GeometryCollection" has a member with the name "coordinates". The value of the "coordinates" member is an array. The structure of the elements in this array is determined by the type of geometry. GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.
~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1

This matcher should limit its scope by only taking an argument of an individual array value (i.e. not the entire property).

A position is an array of numbers. There MUST be two or more elements. The first two elements are longitude and latitude, or easting and northing, precisely in that order and using decimal numbers. Altitude or elevation MAY be included as an optional third element.

Implementations SHOULD NOT extend positions beyond three elements because the semantics of extra elements are unspecified and ambiguous. Historically, some implementations have used a fourth element to carry a linear referencing measure (sometimes denoted as "M") or a numerical timestamp, but in most situations a parser will not be able to properly interpret these values. The interpretation and meaning of additional elements is beyond the scope of this specification, and additional elements MAY be ignored by parsers.
~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.1

An OPTIONAL third-position element SHALL be the height in meters above or below the WGS 84 reference ellipsoid. In the absence of elevation values, applications sensitive to height or depth SHOULD interpret positions as being at local ground or sea level.
~https://datatracker.ietf.org/doc/html/rfc7946#section-4

This matcher will enforce three-member arrays. Separate matchers will be used for 2D arrays and for validating that geometries and features have a correct coordinates key.

WGS84 Bounds: -180.0000, -90.0000, 180.0000, 90.0000
Projected Bounds: -180.0000, -90.0000, 180.0000, 90.0000
~ https://spatialreference.org/ref/epsg/wgs-84/
Also, see https://datatracker.ietf.org/doc/html/rfc7946#section-4

Any values outside of these WGS-84 standards should fail.

The third array member must be a number from -Infinity to Infinity. The spec does not mandate a particular unit for this value (i.e. meters, feet, etc.)

Valid GeoJSON Examples

A three-element array of points:

{
   "type": "Feature",
   "geometry": {
       "type": "Point",
       "coordinates": [102.0, 0.5, 20]
   },
   "properties": {
       "prop0": "value0"
   }
}

An array of coordinates:

{
   "type": "Feature",
   "geometry": {
       "type": "LineString",
       "coordinates": [
           [102.0, 0.0, 0],
           [103.0, 1.0, 100],
           [104.0, 0.0, -20],
           [105.0, 1.0, 45]
       ]
   }
}
{
   "type": "Polygon", // Or LineString
   "coordinates": [
       [
           [100.0, 0.0, 0],
           [101.0, 0.0, 10],
           [101.0, 1.0, -10],
           [100.0, 1.0, 35],
           [100.0, 0.0, 0]
       ]
   ]
}

An array of arrays of coordinates:

{
   "type": "MultiLineString",
   "coordinates": [
       [
           [170.0, 45.0, 2], [180.0, 45.0, 5]
       ], [
           [-180.0, 45.0, 5], [-170.0, 45.0, 100]
       ]
   ]
}

Example Matcher Usage

expect([22, -34.549, 0]).isValid3DCoordinate()
expect([22, -34.549]).not.isValid3DCoordinate()
expect({ coordinates: [22, -34.549, 22, 0] }).not.isValid3DCoordinate()

Passing Tests

Values in Range

  • Each hemisphere
    • [0, 0, 0], [102.0, 0.5, 1000], [172.0, -15, -1000], [-10.9, 77, 5000], [-152.0, -33.33333, -5000]
  • At the boundaries
    • [180, 0, Infinity], [-180, 0, Infinity], [0, 90, Infinity], [0,-90, Infinity], [180, 90, -Infinity], [180,-90, -Infinity], [-180, 90, -Infinity], [-180,-90, -Infinity]

Failing Tests

Coordinates input not an array

  • undefined
  • null
  • Booleans: true, false
  • Numbers:200, -200, Infinity, -Infinity, NaN
  • Objects: { coordinates: [0, 0, 0] }
  • Strings: '', 'Random Coordinate', '[0, 0, 0]', '[[0, 0, 0], [0, 0, 0]]'

Incorrect number of array elements

  • [ ], [ 20 ], [20, 30], [20, 30, 0, 20, 30, 0, 20, 30, 0]

Coordinates out of range

  • Latitude: [0, 90.0000001, 0], [0, -90.0000001, 0], [0, 900000, 0], [0, -900000, 0]
  • Longitude: [180.0000001, 0, 0], [-180.0000001, 0, 0], [1800000, 0, 0], [-1800000, 0, 0]
  • Both: [181, 91, 0], [181, -91, 0], [-181, 91, 0], [-181, -91, 0]

Coordinates have non-number values

  • Each of the values in "Coordinates input not an array" paired with a valid coordinate:
    • [<value>, 0, 0]
    • [0, <value>, 0]
    • [<value>, <value>, 0]
  • Good Lon/Lat paired with non-number for third element: [0, 0, <value>]
    • undefined
    • null
    • Booleans: true, false
    • Numbers: NaN
    • Objects: { coordinates: [0, 0, 0] }
    • Strings: 'Random Coordinate', '[0, 0, 0]', '[[0, 0, 0], [0, 0, 0]]'

Array nested too deeply

  • [ [ [1, 1, 0], [0, 0, 0] ] ]
  • [ [ [10, 20, 0], [2, 59, 0] ] ]
  • [ [ [10, 20, 0], [2, 90, 0], [95, 5, 0] ] ]
  • [ [ [ [1, 1, 0], [0, 0, 0] ] ] ], [ [ [ [ [2, 2, 0], [3, 3, 0] ] ] ] ]

toHave3DBoundingBox

Description

Bounding boxes are an optional GeoJSON property that describe a box of longitude boundaries that run along meridians and latitude boundaries that are parallel to the equator. A 2D Bounding Box only describes longitude and latitude boundaries, whereas a 3D Bounding Box describes an additional min and max altitude/depth value.

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections. The value of the bbox member MUST be an array of length 2*n where n is the number of dimensions represented in the contained geometries, with all axes of the most southwesterly point followed by all axes of the more northeasterly point. The axes order of a bbox follows the axes order of geometries.

The "bbox" values define shapes with edges that follow lines of constant longitude, latitude, and elevation.
~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

This matcher evaluates any Geometry, GeometryCollection, Feature, or FeatureCollection object to ensure it has a valid 2D bounding box. It will not check any subsets of these objects (i.e. it will not check all "geometries" in the collection also have 3D bounding boxes).

It takes an optional argument of a valid bounding box array, bboxEquals. If present, the feature must have a bounding box that exactly equals this value.

Example Matcher Usage

const testCollection = {
    type: "FeatureCollection",
    bbox: [-100.0, -10, 10, 49.5],
    features: [
        {
            type: "Feature",
            geometry: {...}
        },
        {
            type: "Feature",
            bbox: [-20, -5, 0, 0, 5, 200]
            geometry: {...}
        }
    ]
}
const testPolygon = {
    type: "Polygon",
    bbox: [-10, -10, 0, 10, 10, 0],
    coordinates: [
        [
            [-10.0, -10.0],
            [10.0, -10.0],
            [10.0, 10.0],
            [-10.0, -10.0]
        ]
    ]
}

expect(testPolygon).toHave3DBoundingBox()
expect(testCollection.features[1]).toHave3DBoundingBox([-20, -5, 0, 0, 5, 200])


expect(testCollection).not.toHave3DBoundingBox()
expect(testCollection.features[0]).not.toHave3DBoundingBox()
expect(testCollection.features[1]).not.toHave3DBoundingBox([-10, -10, 0, 10, 10, 0])
expect(testPolygon.bbox).not.toHave3DBoundingBox()

Passing Tests

Valid GeoJSON Objects with a BBox

Check with and without optional bboxEquals

  • Each of the seven Geometry, Feature, and FeatureCollection valid objects

Valid GeoJSON Objects with a BBox

A GeometryCollection, Feature, and FeatureCollection that has a BBox, but

  • one or more components do not
  • one or more have a 2D bounding box

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry, Feature, and FeatureCollection objects when invalid
  • Arrays: [], [0, 0], [0, 0, 0], [[-20], [10], [-10], [20]], [0, 0, 0, 0], [0, 0, 0, 0, 0, 0]
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • {}
  • '',
  • 'Random Feature',
  • JSON.stringify({
        type: 'Feature',
        geometry: null,
        properties: null
    })

Valid GeoJSON Objects without a BBox

Each of the seven Geometry, Feature, and FeatureCollection valid objects

Valid GeoJSON Objects with Component BBox but no Overall BBox

A GeometryCollection, Feature, and FeatureCollection that has no BBox, but the geometries do

Valid GeoJSON Objects with a BBox, bboxEquals Does Not Match

Each of the seven Geometry, Feature, and FeatureCollection valid objects

Incorrect number of array elements in bboxEquals

All items in "Invalid Inputs To Matcher" except the 4 element array

isValid3DBoundingBox

Description

Bounding boxes are an optional GeoJSON property that describe a box of longitude boundaries that run along meridians and latitude boundaries that are parallel to the equator. A 2D Bounding Box only describes longitude and latitude boundaries, whereas a 3D BBox describes an additional min and max altitude/depth value.

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections. The value of the bbox member MUST be an array of length 2*n where n is the number of dimensions represented in the contained geometries, with all axes of the most southwesterly point followed by all axes of the more northeasterly point. The axes order of a bbox follows the axes order of geometries.

The "bbox" values define shapes with edges that follow lines of constant longitude, latitude, and elevation.
~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Format

The values of a "bbox" array are [west, south, east, north], not [minx, miny, maxx, maxy].
~https://datatracker.ietf.org/doc/html/rfc7946#appendix-B.1

The 3D BBox spec is not explicit, but can be inferred as: [west, south, depth, east, north, altitude].

The Antimeridian

In the case of objects spanning the antimeridian, a bounding box becomes required.

Consider a set of point Features within the Fiji archipelago, straddling the antimeridian between 16 degrees S and 20 degrees S. The southwest corner of the box containing these Features is at 20 degrees S and 177 degrees E, and the northwest corner is at 16 degrees S and 178 degrees W. The antimeridian-spanning GeoJSON bounding box for this FeatureCollection is

"bbox": [177.0, -20.0, -178.0, -16.0]

and covers 5 degrees of longitude. The complementary bounding box for the same latitude band, not crossing the antimeridian, is

"bbox": [-178.0, -20.0, 177.0, -16.0]

and covers 355 degrees of longitude.

The latitude of the northeast corner is always greater than the latitude of the southwest corner, but bounding boxes that cross the antimeridian have a northeast corner longitude that is less than the longitude of the southwest corner.
~https://datatracker.ietf.org/doc/html/rfc7946#section-5.2

The Poles

An area that encompasses either the north or south pole will also require a bounding box that meets special use cases.

A bounding box that contains the North Pole extends from a southwest corner of "minlat" degrees N, 180 degrees W to a northeast corner of 90 degrees N, 180 degrees E. Viewed on a globe, this bounding box approximates a spherical cap bounded by the "minlat" circle of latitude.

"bbox": [-180.0, minlat, 180.0, 90.0]

A bounding box that contains the South Pole extends from a southwest corner of 90 degrees S, 180 degrees W to a northeast corner of "maxlat" degrees S, 180 degrees E.

"bbox": [-180.0, -90.0, 180.0, maxlat]

A bounding box that just touches the North Pole and forms a slice of an approximate spherical cap when viewed on a globe extends from a southwest corner of "minlat" degrees N and "westlon" degrees E to a northeast corner of 90 degrees N and "eastlon" degrees E.

"bbox": [westlon, minlat, eastlon, 90.0]

Similarly, a bounding box that just touches the South Pole and forms a slice of an approximate spherical cap when viewed on a globe has the following representation in GeoJSON.

"bbox": [westlon, -90.0, eastlon, maxlat]

Implementers MUST NOT use latitude values greater than 90 or less than -90 to imply an extent that is not a spherical cap.
~https://datatracker.ietf.org/doc/html/rfc7946#section-5.3

Boundaries

Any values outside of the WGS-84 standards should fail.

WGS84 Bounds: -180.0000, -90.0000, 180.0000, 90.0000
Projected Bounds: -180.0000, -90.0000, 180.0000, 90.0000
~ https://spatialreference.org/ref/epsg/wgs-84/
Also, see https://datatracker.ietf.org/doc/html/rfc7946#section-4

Although not explicitly stated, it can be inferred that altitude must be greater than or equal to depth.

Units

The GeoJSON standard does not mandate any particular units for the depth and height, but meters is a common reference and matches well with typical geodesy tools.

Valid GeoJSON Examples

Example of a 3D bbox member with a depth of 100 meters:

   {
       "type": "FeatureCollection",
       "bbox": [100.0, 0.0, -100.0, 105.0, 1.0, 0.0],
       "features": [
       //...
       ]
   }

Example of a 3D bbox member with an altitude between 100 and 950 meters:

   {
       "type": "FeatureCollection",
       "bbox": [100.0, 0.0, 100, 105.0, 1.0, 950],
       "features": [
       //...
       ]
   }

Example of a 3D bbox on features with a FeatureCollection that also has a bounding box:

{
    "type": "FeatureCollection",
    "bbox":  [-10, -10, -100, 10, 10, 950], // -167 to 950meters
         "features": [
             {
                 "type": "Feature",
                 "bbox": [-10.0, -10.0, -167, 10.0, 10.0, 0], // -167 to 0 meters
                 "geometry": {
                     ...
                 }
             },
             {
                 "type": "Feature",
                 "bbox": [-10.0, -10.0, 0, 10.0, 10.0, 800], // 0 to 800 meters
                 "geometry": {
                     ...
                 }
             },
             ...
         ]
}

Example of a 3D bbox that crosses the antimeridian:

{
    "type": "FeatureCollection",
    "bbox": [177.0, -20.0, 0, -178.0, -16.0, 25000], // 0 to 25,000 meters
         "features": [
             ...
         ]
}

Example Matcher Usage

expect([-10.0, -10.0, 0, 10.0, 10.0, 10000]).isValid3DBoundingBox()
expect([100.0, 0.0, -100.0, 105.0, 1.0, 0.0]).isValid3DBoundingBox()
expect([177.0, -20.0, -250, -178.0, -16.0, 300]).isValid3DBoundingBox() // Crosses antimeridian and spans 5 degrees, -250 to 300 meters

expect([-10.0, -10.0, 0, 10.0, 100.0, 0]).not.isValid3DBoundingBox() // North out of bounds
expect([100.0, 0.0, 105.0, 1.0]).not.isValid3DCoordinate() // 2D bounding box
expect({ bbox: [-10.0, -10.0, 0, 10.0, 10.0, 0] }).not.isValid3DCoordinate() // Object instead of bbox array

Passing Tests

Values in Range

  • Each quadrant
    • NW: [-20, 10, 0, -10, 20, 0]
    • NE: [10, 10, 0, 20, 20, 0]
    • SW: [-20, -20, 0, -10, -10, 0]
    • SE: [10, -20, 0, 20, -10, 0]
  • Spanning all quadrants
    • Prime meridian: [-10, -20, 0, 20, 10, 0]
    • Antimeridian: [170, -20, 0, 20, -170, 0]
  • Depth and Elevation
    • Depth: [-10, -20, -100, 20, 10, 0], [-10, -20, -500, 20, 10, -50]
    • Altitude: [170, -20, 0, -170, 20, 100], [-10, -20, 50, 20, 10, 500]
    • Both: [-10, -20, -22.5, 20, 10, 12345.678]

Values at Edge Cases

  • Longitude Boundaries:
    • West: [-180, 10, 0, 20, 20, 0]
    • North: [10, 10, 0, 180, 20, 0]
  • Poles with Cap
    • North: [-180.0, 80, 0, 180.0, 90.0, 0]
    • South: [-180.0, -90, 0, 180.0, -80.0, 0]
  • Poles not Including Cap
    • North: [10, 80, 0, 20, 90.0, 0]
    • South: [-45, -90, 0, -80, -80.0, 0]
  • Spanning all longitudes
    • All longitudes: [-180, 10, 0, 180, 20, 0]
    • All latitudes: [-10, -90, 0, 10, 90, 0]
    • Whole Earth: [-180, -90, 0, 180, 90, 0]
    • Whole Earth with Depth and Altitude: [-180, -90, -100000, 180, 90, 100000]
  • Northern Boundary Equals Southern:
    • [-10, -20, 0, 10, -20, 0]
    • [-10, 20, 0, 10, 20, 0]
  • Eastern Boundary Equals Western
    • [-10, -20, 0, -10, 20, 0]
    • [10, -20, 0, 10, 20, 0]
  • Lower Depth Equals Upper Depth
    • [-10, -20, -220, -10, 20, -220]
  • Lower Altitude Equals Upper Altitude
    • [-10, -20, 330, -10, 20, 330]
  • All zeros
    • [0, 0, 0, 0, 0, 0]

Failing Tests

Bbox input not an array

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Objects: { bbox: [10, 10, 0, 20, 20, 0] }
  • Strings: '', 'Random Coordinate', '[10, 10, 0, 20, 20, 0]'

Incorrect number of array elements

  • [ ], [20], [20, 10], [20, 30, 0], [-10, 30, -5, 40], [20, 30, 0, 20, 30], [20, 30, 0, 20, 30, 0, 2],[20, 30, 0, 20, 30, 0, 20, 30, 0]

Coordinates out of range

  • Latitude: [-10, -90.0000001, 0, 10, 0, 0], [-10, 0, 0, 10, 90.0000001, 0], [-10, -90000, 0, 10, 0, 0], [-10, 0, 0, 10, 90000, 0]
  • Longitude: [-180.0000001, -10, 0, -160, 10, 0], [160, -10, 0, 180.0000001, 10, 0], [-1800000, -10, 0, -160, 10, 0], [160, -10, 0, 1800000, 10, 0]
  • Combination:
    • [-181, -10, 0, 181, 10, 0]
    • [-10, -91, 0, 10, 91, 0]
    • [-181, -91, 0, 10, 10, 0]
    • [-10, -10, 0, 181, 91, 0]
    • [-181, -91, 0, 181, 91, 0]

Illogical BBox

  • Northern Boundary Less Than Southern: [-10, 20, 0, 10, -20, 0]
  • Altitude Less Than Depth: [-10, -20, 200, 20, 10, 150]

BBox has non-numeric values

  • Each of the values in "Bbox input not an array" paired with valid inputs:
    • [<value>, -10, 0, 10, 10, 0]
    • [-10, <value>, 0, 10, 10, 0]
    • [-10, -10, <value>, 10, 10, 0]
    • [-10, -10, 0, <value>, 10, 0]
    • [-10, -10, 0, 10, <value>, 0]
    • [-10, -10, 0, 10, 10, <value>]
    • [<value>, <value>, <value>, <value>, <value>, <value>]

BBox values are arrays of numbers

[[-20], [10], [0], [-10], [20], [0]]

[Bug]: Geometry Matchers do Not Check for BBox

What happened?

The seven geometry matchers do not check for valid Bounding Boxes. Per the spec:

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Both 2D and 3D bounding boxes should pass, and non-valid bounding boxes should throw an error.

The tests, code, and issues need to be revised for:

Version

v1.0.0-beta.13

What operating system are you seeing the problem on?

Windows

What version of Node are you seeing the problem on?

16

Relevant log output

None

Are you able/willing to make the change? (It's ok if you're not!)

Yes

Code of Conduct

isValid2DCoordinate

Description

For testing 3D coordinates, see isValid3DCoordinate. For testing either, see isValidCoordinate.

This is one of the first building block matchers needed. Coordinates are the basis of all objects, and we need a way to reliably test that they have been formatted appropriately.

A GeoJSON Geometry object of any type other than "GeometryCollection" has a member with the name "coordinates". The value of the "coordinates" member is an array. The structure of the elements in this array is determined by the type of geometry. GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.
~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1

This matcher should limit its scope by only taking an argument of an individual array value (i.e. not the entire property).

A position is an array of numbers. There MUST be two or more elements. The first two elements are longitude and latitude, or easting and northing, precisely in that order and using decimal numbers. Altitude or elevation MAY be included as an optional third element.

Implementations SHOULD NOT extend positions beyond three elements because the semantics of extra elements are unspecified and ambiguous. Historically, some implementations have used a fourth element to carry a linear referencing measure (sometimes denoted as "M") or a numerical timestamp, but in most situations a parser will not be able to properly interpret these values. The interpretation and meaning of additional elements is beyond the scope of this specification, and additional elements MAY be ignored by parsers.
~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.1

This matcher will enforce two-member arrays. Separate matchers will be used for 3D arrays and for validating that geometries and features have a correct coordinates key.

WGS84 Bounds: -180.0000, -90.0000, 180.0000, 90.0000
Projected Bounds: -180.0000, -90.0000, 180.0000, 90.0000
~ https://spatialreference.org/ref/epsg/wgs-84/
Also, see https://datatracker.ietf.org/doc/html/rfc7946#section-4

Any values outside of these WGS-84 standards should fail.

Valid GeoJSON Coordinate Examples

A two-element array of points:

{
   "type": "Feature",
   "geometry": {
       "type": "Point",
       "coordinates": [102.0, 0.5]
   },
   "properties": {
       "prop0": "value0"
   }
}

An array of coordinates:

{
   "type": "Feature",
   "geometry": {
       "type": "LineString",
       "coordinates": [
           [102.0, 0.0],
           [103.0, 1.0],
           [104.0, 0.0],
           [105.0, 1.0]
       ]
   }
}
{
   "type": "Polygon", // Or LineString
   "coordinates": [
       [
           [100.0, 0.0],
           [101.0, 0.0],
           [101.0, 1.0],
           [100.0, 1.0],
           [100.0, 0.0]
       ]
   ]
}

An array of arrays of coordinates:

{
   "type": "MultiLineString",
   "coordinates": [
       [
           [170.0, 45.0], [180.0, 45.0]
       ], [
           [-180.0, 45.0], [-170.0, 45.0]
       ]
   ]
}
{
   "type": "MultiPolygon",
   "coordinates": [
       [
           [
               [180.0, 40.0], [180.0, 50.0], [170.0, 50.0],
               [170.0, 40.0], [180.0, 40.0]
           ]
       ],
       [
           [
               [-170.0, 40.0], [-170.0, 50.0], [-180.0, 50.0],
               [-180.0, 40.0], [-170.0, 40.0]
           ]
       ]
   ]
}

In each case, the matcher should only get passed the value of a single coordinate, not the object itself.

Example Matcher Usage

expect([22, -34.549]).isValid2DCoordinate()
expect([22, -34.549, 22]).not.isValid2DCoordinate()
expect({coordinates: [22, -34.549, 22]}).not.isValid2DCoordinate()

Passing Tests

Values in Range

  • Each hemisphere
    • [0, 0], [102.0, 0.5], [172.0, -15], [-10.9, 77], [-152.0, -33.33333]
  • At the boundaries
    • [180, 0], [-180, 0], [0, 90], [0,-90], [180, 90], [180,-90], [-180, 90], [-180,-90]

Failing Tests

Coordinates input not an array

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Objects: { coordinates: [0, 0] }
  • Strings: '', 'Random Coordinate', '[0, 0]', '[[0, 0], [0, 0]]'

Incorrect number of array elements

  • [ ], [ 20 ], [20, 30, 0], [20, 30, 0, 20, 30, 0, 20, 30, 0]

Coordinates out of range

  • Latitude: [0, 90.0000001], [0, -90.0000001], [0, 900000], [0, -900000]
  • Longitude: [180.0000001, 0], [-180.0000001, 0], [1800000, 0], [-1800000, 0]
  • Both: [181, 91], [181, -91], [-181, 91], [-181, -91]

Coordinates have non-number values

  • Each of the non-numeric values in "Coordinates input not an array" paired with a valid coordinate:
    • [<value>, 0]
    • [0, <value>]
    • [<value>, <value>]

Array nested too deeply

  • [ [ [1, 1], [0, 0] ] ]
  • [ [ [10, 20], [2, 59] ] ]
  • [ [ [10, 20], [2, 90], [95, 5] ] ]
  • [ [ [ [1, 1], [0, 0] ] ] ], [ [ [ [ [2, 2], [3, 3] ] ] ] ]

toHaveBoundingBox

Description

Bounding boxes are an optional GeoJSON property that describe a box of longitude boundaries that run along meridians and latitude boundaries that are parallel to the equator. A 2D Bounding Box only describes longitude and latitude boundaries, whereas a 3D Bounding Box describes an additional min and max altitude/depth value.

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections. The value of the bbox member MUST be an array of length 2*n where n is the number of dimensions represented in the contained geometries, with all axes of the most southwesterly point followed by all axes of the more northeasterly point. The axes order of a bbox follows the axes order of geometries.

The "bbox" values define shapes with edges that follow lines of constant longitude, latitude, and elevation.
~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

This matcher evaluates any Geometry, GeometryCollection, Feature, or FeatureCollection object to ensure it has a valid 2D or 3D bounding box. It will not check any subsets of these objects (i.e. it will not check all "geometries" in the collection also have bounding boxes).

It takes an optional argument of a valid bounding box array, bboxEquals. If present, the feature must have a bounding box that exactly equals this value.

Example Matcher Usage

const testCollection = {
    type: "FeatureCollection",
    bbox: [-100.0, -10, 10, 49.5],
    features: [
        {
            type: "Feature",
            geometry: {...}
        },
        {
            type: "Feature",
            bbox: [-20, -5, 0, 5]
            geometry: {...}
        }
    ]
}
const testPolygon = {
    type: "Polygon",
    bbox: [-10, -10, 10, 10],
    coordinates: [
        [
            [-10.0, -10.0],
            [10.0, -10.0],
            [10.0, 10.0],
            [-10.0, -10.0]
        ]
    ]
}

expect(testCollection).toHaveBoundingBox()
expect(testCollection).toHaveBoundingBox([-100.0, -10, 10, 49.5])
expect(testCollection.features[1]).toHaveBoundingBox()
expect(testCollection.features[1]).toHaveBoundingBox([-20, -5, 0, 5])
expect(testPolygon).toHaveBoundingBox()


expect(testCollection).not.toHaveBoundingBox([-10, -10, 10, 10])
expect(testCollection.features[0]).not.toHaveBoundingBox()
expect(testPolygon.bbox).not.toHaveBoundingBox()

Passing Tests

Valid GeoJSON Objects with a BBox

Check with and without optional bboxEquals

  • Each of the seven Geometry, Feature, and FeatureCollection valid objects

Valid GeoJSON Objects with a BBox

A GeometryCollection, Feature, and FeatureCollection that has a BBox, but one or more components do not

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry, Feature, and FeatureCollection objects when invalid
  • Arrays: [], [0, 0], [0, 0, 0], [[-20], [10], [-10], [20]], [0, 0, 0, 0], [0, 0, 0, 0, 0, 0]
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • {}
  • '',
  • 'Random Feature',
  • JSON.stringify({
        type: 'Feature',
        geometry: null,
        properties: null
    })

Valid GeoJSON Objects without a BBox

Each of the seven Geometry, Feature, and FeatureCollection valid objects

Valid GeoJSON Objects with Component BBox but no Overall BBox

A GeometryCollection, Feature, and FeatureCollection that has no BBox, but the geometries do

Valid GeoJSON Objects with a BBox, bboxEquals Does Not Match

Each of the seven Geometry, Feature, and FeatureCollection valid objects

Incorrect number of array elements in bboxEquals

All items in "Invalid Inputs To Matcher" except the 4 and 6 element arrays

Add Typescript Definitions for Matchers

Right now, the matchers all work as designed, but there is no Typescript support or Intellisense to go along with it.

To add this, it will require

  • index.d.ts file
  • Update package.json "types" member
  • Ensure the types get exported in the distribution
  • Update README instructions
  • Update CONTRIBUTING instructions

For more information and references, see:

Export core functionality functions outside of Jest

The core functionality should be exportable outside of the Jest environment. For example, I should be able to do the following:

// myScript.js

const jestGeoJSON= require(jest-geojson/core)

const goodCoord = jestGeoJSON.coordinates.valid2DCoordinate([117.19, 73.3])

console.log (goodCoord) // true

By having this kind of functionality, developers can use the core logic within other scripts and applications. If desired, it would even support porting jest-geojson to other testing frameworks.

To accomplish this, the index.js file will need to export objects in the following structure:

β”œβ”€β”€ jestGeoJSON
β”‚   β”œβ”€β”€ matchers
β”‚   β”‚   β”œβ”€β”€ boundingBox
β”‚   β”‚   β”œβ”€β”€ coordinate
β”‚   β”‚   β”œβ”€β”€ feature
β”‚   β”‚   β”œβ”€β”€ featureCollection
β”‚   β”‚   β”œβ”€β”€ functional
β”‚   β”‚   β”œβ”€β”€ geometry
β”‚   β”‚   └── geometryCollection
β”œβ”€β”€ core
β”‚   β”‚   β”œβ”€β”€ boundingBoxes
β”‚   β”‚   β”œβ”€β”€ coordinates
β”‚   β”‚   β”œβ”€β”€ features
β”‚   β”‚   β”œβ”€β”€ featureCollections
β”‚   β”‚   β”œβ”€β”€ functional
β”‚   β”‚   β”œβ”€β”€ geometries
β”‚   β”‚   └── geometryCollections

For testing this functionality, all individual functions already get tested with the Jest framework itself. The only additional test needs to be a core.test.js suite that verifies all expected functions are present on the exported object.

This will also require an update to JestSetup.js to be more specific in how it loads all the matchers.

toBeFeatureCollection

Description

This is a building block matcher. FeatureCollections contain Feature objects and additional properties. We need a way to reliably test that they have been formatted appropriately.

This matcher tests that an object is a valid feature with any geometry type. Any non-conforming values should fail.

  • It must have a "type" member equal to 'FeatureCollection'
  • It must have a "features" member, but this may be an empty array ([]).

GeoJSON object with the type "FeatureCollection" is a FeatureCollection object. A FeatureCollection object has a member with the name "features". The value of "features" is a JSON array. Each element of the array is a Feature object as defined above. It is possible for this array to be empty.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.3

The word "Collection" in "FeatureCollection" and "GeometryCollection" does not have any significance for the semantics of array members. The "features" and "geometries" members, respectively, of these objects are standard ordered JSON arrays, not unordered sets.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-1.4

Foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members (to include "id") should get checked for validity of any type; they may contain anything that is valid JSON.

FeatureCollection objects are prohibited from having a "coordinates", "geometries", "geometry", or "properties" member.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Bounding boxes, if present, must validate (see isValidBoundingBox).

A GeoJSON object represents a Geometry, Feature, or collection of Features.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Valid GeoJSON Feature Examples

{
    "type": "FeatureCollection",
    "features": [{
        "type": "Feature",
        "geometry": {
            "type": "Point",
            "coordinates": [102.0, 0.5]
        },
        "properties": {
            "prop0": "value0"
        }
    }, {
        "type": "Feature",
        "geometry": {
            "type": "LineString",
            "coordinates": [
                [102.0, 0.0],
                [103.0, 1.0],
                [104.0, 0.0],
                [105.0, 1.0]
            ]
        },
        "properties": {
            "prop0": "value0",
            "prop1": 0.0
        }
    }, {
        "type": "Feature",
        "geometry": {
            "type": "Polygon",
            "coordinates": [
                [
                    [100.0, 0.0],
                    [101.0, 0.0],
                    [101.0, 1.0],
                    [100.0, 1.0],
                    [100.0, 0.0]
                ]
            ]
        },
        "properties": {
            "prop0": "value0",
            "prop1": {
                "this": "that"
            }
        }
    }]
}

An empty features

{
    "type": "FeatureCollection",
    "features": []
}

In all cases, the matcher should only get passed an object in its entirety, not the individual components.

Example Matcher Usage

const testFeatureCollectionCollection = {
    "type": "FeatureCollection",
    "features": [{
        "type": "Feature",
        "geometry": {
            "type": "Point",
            "coordinates": [102.0, 0.5]
        }
    },
    ...
    ]
}
const multiPoint = {
    type: "MultiPoint",
    coordinates: [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}

expect(testFeatureCollection).toBeFeature()

expect(multiPoint).not.toBeFeature()
expect(testFeatureCollection.features).not.toBeFeature('Polygon')

Passing Tests

This matcher should use core functions to test object validity.

Values in Range

  • A FeatureCollection containing one feature with each of the seven geometry types
  • A FeatureCollection containing all of the above features

Stress Test

  • A FeatureCollection containing 10 features of each of the seven geometry types (70 total)

Features May Be an Empty Array

const testFeatureCollection = {
    type: 'FeatureCollection',
    features: []
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [{
        type: "MultiPoint",
        coordinates:[[0, 0], [1, 1, 100]]
    }],
    bbox: <input>
}
  • Illogical
const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [{
        type: "MultiPoint",
        coordinates:[[0, 0], [1, 1]]
    }],
    bbox: [-30.0, -30.0, -20.0, -20.0]
}
  • Redundant
const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [{
        type: "MultiPoint",
        coordinates:[[0, 0], [0, 0]]
    }],
    bbox: [0, 0, 0, 0]
}

ID is a Foreign Member, Can Be Anything

  • null, undefined
  • Boolean: true, false
  • Strings: 'ABCD', 'Test 1', '1', '', '[[[180, 10.2, -125], [-180, 90, 35000]]], [{}]'
  • Numbers: 0, 200, -200, Infinity, -Infinity, NaN
  • Array: [], [1]
  • Object: {}, {id: 1}
const testFeatureCollection = {
    type: 'FeatureCollection',
    id: <input>,
    features: []
}

Other Foreign Properties Allowed

  • Single
const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [],
    foreign: true
}
  • Multiple
const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [],
    id: '#1',
    foreign1: true,
    foreign2: 33
}
  • Foreign property that would normally be a valid GeoJSON object
const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [],
    foreignGeometry: multiPoint,
    Geometry: multiPoint // Note captitalized 'G'
}

Failing Tests

Feature Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays: [25, 35, 45000], [ ]
  • Strings: '', 'Random FeatureCollection'
  • Stringified JSON:
    JSON.stringify({
      type: 'FeatureCollection',
      features: [{
          type: 'Feature',
          properties: {},
          geometry: {
              type: "MultiPoint",
              coordinates:[[0, 0], [1, 1, 100]]
          }
      }]
    })

Invalid Features

  • A FeatureCollection containing one invalid feature with each of the seven geometry types
  • A FeatureCollection containing all seven valid features plus one invalid

Type Value Incorrect

Input:

  • Each of the values from "Feature Input Not an Object", plus
  • 'Point', 'MultiPoint', 'LineString', 'MultiLineString' 'Polygon', 'MultiPolygon', and 'GeometryCollection', 'Feature', or
  • 'FEATURECOLLECTION', 'featurecollection'
const testFeatureCollection = {
    type: <input>,
    features: []
}

Features Invalid

  • Each of the values from "Feature Input Not an Object" except the empty array, both contained in an array and not contained in an array
const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [<input>]
}
const testFeatureCollection = {
    type: 'FeatureCollection',
    features: <input>
}

Invalid Bounding Box

Input:

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [...],
    bbox: <input>
}

Contains Prohibited Properties

Coordinates

const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [...],
    coordinates:[[0, 0], [1, 1]]
}

Geometries

const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [...],
    geometries: [
        {
            type: 'Point',
            coordinates: [102.0, 0.5]
        }, {
            type: 'Point',
            coordinates: [122.0, -10.25]
        }
    ]
}

Geometry

const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [...],
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    id: 33
}

Properties

const testFeatureCollection = {
    type: 'FeatureCollection',
    features: [...],
    properties: {
        someProp: true
    }
}

Missing Required Properties

  • No Type
const testFeatureCollection = {
    features: []
}
  • No Features
const testFeatureCollection = {
    type: 'FeatureCollection'
}

toBeMultiPolygonGeometry

Description

This is a building block matcher. Geometries are the basis of all objects, and we need a way to reliably test that they have been formatted appropriately.

This matcher focuses only on "MultiPolygon". All coordinates must be valid coordinates per the WGS-84 standard. Any non-conforming values should fail. However, the "coordinates" property MAY have an empty array ([ ]) as a valid value. It MAY NOT have an array of empty arrays as a value ([ [ [ ], [ ] ] ]).

A Geometry object represents points, curves, and surfaces in coordinate space. Every Geometry object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • The value of a Geometry object's "type" member MUST be one of the seven geometry types (per Section 1.4: "Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", and "GeometryCollection").
  • A GeoJSON Geometry object of any type other than "GeometryCollection" has a member with the name "coordinates". The value of the "coordinates" member is an array. The structure of the elements in this array is determined by the type of geometry. GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1

For type "MultiPolygon", the "coordinates" member is an array of Polygon coordinate arrays.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.7

The geometry object should have a "type" and a "coordinates" property. It is prohibited from having a "geometry", "properties", or "features" property.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.
https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

Although illogical, it is NOT PROHIBITED from having a "geometries" property, although if present it would be considered a foreign member.

Other foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members should get checked for validity of any type; they may contain anything that is valid JSON.

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Bounding boxes, if present, must validate (see isValidBoundingBox).

A GeoJSON object represents a Geometry, Feature, or collection of Features.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Valid GeoJSON MultiPolygon Examples

Two polygons, one without a hole and one with:

{
    "type": "MultiPolygon",
    "coordinates": [
        [
            [
                [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.2, 0.8],
                [100.8, 0.8],
                [100.8, 0.2],
                [100.2, 0.2]
            ]
        ]
    ]
}

A single polygon:

{
    "type": "MultiPolygon",
    "coordinates": [
        [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ]
    ]
}

Antimeridian Cutting:

{
    "type": "MultiPolygon",
    "coordinates": [
        [
            [
                [180.0, 40.0], [180.0, 50.0], [170.0, 50.0],
                [170.0, 40.0], [180.0, 40.0]
            ]
        ],
        [
            [
                [-170.0, 40.0], [-170.0, 50.0], [-180.0, 50.0],
                [-180.0, 40.0], [-170.0, 40.0]
            ]
        ]
    ]
}

An empty coordinates

{
    "type": "MultiPolygon",
    "coordinates": [ ]
}

In each case, the matcher should only get passed the object in its entirety, not the individual components, and not as part of another collection object.

Example Matcher Usage

const multiPolygon = {
    "type": "MultiPolygon",
    "coordinates": [
        [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ],
        ...
    ]
}
const multiLineString = {
    type: "MultiLineString",
    coordinates: [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]
    ]
}

expect(multiPolygon).toBeMultiPolygonGeometry()

expect(multiLineString).not.toBeMultiPolygonGeometry()
expect(multiPolygon.coordinates).not.toBeMultiPolygonGeometry()

Passing Tests

This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.

Values in Range

  • Counterclockwise wound
    • 2D points, 3D points, Mixed 2D and 3D
    • Both without and with holes
  • Clockwise wound
    • 2D points, 3D points, Mixed 2D and 3D
    • Both without and with holes
  • Cutting the antimeridian
  • Spanning the antimeridian
    • Note: this is an assumption inferred from proximity. No way to tell without a bounding box on a polygon feature
  • Redundant
    • Exterior ring all the same point
    • Interior rings all the same point
    • Interior rings outside exterior rings
  • Stressed: A 30 element multi polygon, each element containing 30 coordinates
const testMultiPolygon = {
    type: "MultiPolygon",
    coordinates: [
        [<input>]
    ]
}
  • Empty Coordinate
const testMultiPolygon = {
    type: "MultiPolygon",
    coordinates: [ ]
}

Foreign Properties Allowed

  • ID (String and number input: 'Test 1', 1
const testMultiPolygon = {
    type: "MultiPolygon",
    id: <input>,
    coordinates: ...
}
  • Non alphanumeric ID
    ID Normally needs to be a letter or number, but because it is a foreign member on a geometry object, the GeoJSON standard takes no consideration about it.
const testMultiPolygon1 = {
    type: "MultiPolygon",
    id: null,
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ]
}
  • Geometries
const testMultiPolygon2 = {
    type: "MultiPolygon",
    geometries: testMultiPolygon1,
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ]
}
  • Multiple
const testMultiPolygon3 = {
    type: "MultiPolygon",
    someRandomProp: true,
    geometries: testMultiPolygon2,
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ]
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testMultiPolygon = {
    type: 'MultiPolygon',
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ],
    bbox: <input>
}
  • Illogical
const testMultiPolygon = {
    type: 'MultiPolygon',
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ],
    bbox: [-30.0, -30.0, -20.0, -20.0]
}
  • Redundant
const testMultiPolygon = {
    type: 'MultiPolygon',
    coordinates: [
        [
            [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
        ]
    ],
    bbox: [0, 0, 0, 0]
}

Failing Tests

Geometry Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays:
    • [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
    • [1, 1]
    • [ ]
  • Strings: '', 'Random Geometry', '[0, 0]', '[[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]]'
  • Stringified JSON:
    JSON.stringify({
      type: "MultiPolygon",
      coordinates: [
          [
              [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
          ]
      ]
    })

Invalid Coordinates

  • Each of the values from "Geometry Input Not an Object":
const testMultiPolygon= {
    type: "MultiPolygon",
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], <input>, [0, 0]]
        ]
    ]
}

Coordinates Out of Range

This only tests a handful of invalid coordinates to verify behavior. Detailed coordinate validation occurs in isValidCoordinate

Input:

  • [[181, 0], [0, 1], [1, 1], [1, 0], [181, 0]]
  • [[0, 0], [0, 91], [1, 1], [1, 0], [0, 0]]
  • [[0, 0], [0, 1], [-181, 1], [1, 0], [0, 0]]
  • [[0, -181], [0, 1], [1, 1], [1, 0], [0, -181]]
  • [[0, 0, 0, 0]]
const testMultiPolygon= {
    type: "MultiPolygon",
    coordinates: [
        [<input>]
    ]
}

Not Enough Coordinates

  • The each polygon linestring array in coordinates must have four or more positions.

Input:

  • [[[0, 0]]]
  • [[[0, 0], [1, 1]]]
  • [[[0, 0], [1, 1]], [[1, 0]]]
const testMultiPolygon= {
    type: "MultiPolygon",
    coordinates: [<input>]
}

Final Coordinate does not Match First Coordinate

Input:

  • [[0, 0], [0, 1], [1, 1], [1, 0]]
  • [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], [[0, 0], [0, 1], [1, 1], [1, 0]]
  • [[0, 0], [0, 1], [1, 1], [1, 0], [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
const testMultiPolygon= {
    type: "MultiPolygon",
    coordinates: [
        [<input>]
    ]
}

Type Value Incorrect

Input:

  • Each of the values from "Geometry Input Not an Object", plus
  • 'Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', and 'GeometryCollection', or
  • 'MULTIPOLYGON', 'multipolygon'

Input:

  • [[[0, 0]]]
  • [[[0, 0], [1, 1]]]
  • [[[0, 0], [1, 1]], [[1, 0]]]
const testMultiPolygon= {
    type: "MultiPolygon",
    coordinates: <input>
}

Contains Prohibited Properties

Geometry

const testMultiPolygon = {
    type: "MultiPolygon",
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ],
    geometry: {
        type: "Point",
        coordinates: [102.0, 0.5]
    }
}

Properties

const testMultiPolygon = {
    type: "MultiPolygon",
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ],
    properties: {
        prop1: true
    }
}

Features

const testMultiPolygon = {
    type: "MultiPolygon",
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ],
    features: [{
        type: "Feature",
        geometry: {
            type: "Point",
            coordinates: [102.0, 0.5]
            },
        properties: {
           prop0: "value0"
        }
    }]
}

Missing Required Properties

  • No Type
const testMultiPolygon = {
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ]
}
  • No Coordinates
const testMultiPolygon = {
    type: "MultiPolygon"
}

Coordinates is an Array of Null Arrays

  • Test for 1x, 2x, 3x
  • No Type
const testMultiPolygon = {
    type: "MultiPolygon",
    coordinates: [[[[<arrays>]]]]
}

Invalid Bounding Box

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testMultiPolygon = {
    type: 'MultiPolygon',
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ],
    bbox: <input>
}

toBeMultiPointGeometry

Description

This is a building block matcher. Geometries are the basis of all objects, and we need a way to reliably test that they have been formatted appropriately.

This matcher focuses only on "MultiPoint". All coordinates must be valid coordinates per the WGS-84 standard. Any non-conforming values should fail. However, the "coordinates" property MAY have an empty array ([ ]) as a valid value.

A Geometry object represents points, curves, and surfaces in coordinate space. Every Geometry object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • The value of a Geometry object's "type" member MUST be one of the seven geometry types (per Section 1.4: "Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", and "GeometryCollection").
  • A GeoJSON Geometry object of any type other than "GeometryCollection" has a member with the name "coordinates". The value of the "coordinates" member is an array. The structure of the elements in this array is determined by the type of geometry. GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.

~https://datatracker.ietf.org/doc/html/rfc7946#section-3.1

For type "MultiPoint", the "coordinates" member is an array of positions.
https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.3

The geometry object should have a "type" and a "coordinates" property. It is prohibited from having a "geometry", "properties", or "features" property.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.
https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

Although illogical, it is NOT PROHIBITED from having a "geometries" property, although if present it would be considered a foreign member.

Other foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members should get checked for validity of any type; they may contain anything that is valid JSON.

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Bounding boxes, if present, must validate (see isValidBoundingBox).

A GeoJSON object represents a Geometry, Feature, or collection of Features.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Valid GeoJSON MultiPoint Examples

Multiple Points

    {
        "type": "MultiPoint",
        "coordinates": [
            [100.0, 0.0],
            [101.0, 1.0]
        ]
    }

An empty coordinate

{
    "type": "MultiPoint",
    "coordinates": [ ]
}

Contained as an object within a geometries array on a GeometryCollection:

{
    "type": "GeometryCollection",
    "geometries": [{
        "type": "Point",
        "coordinates": [100.0, 0.0]
    }, {
        "type": "MultiPoint",
        "coordinates": [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }]
}

In each case, the matcher should only get passed the object in its entirety, not the individual components, and not as part of another collection object.

Example Matcher Usage

const multiPoint1 = {
    "type": "MultiPoint",
    "coordinates": [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}
const multiPoint2 = {
    type: "MultiPoint",
    coordinates: [[100.0, 0.0, 2000]]
}
const multiPoint3 = {
    type: "LineString",
    coordinates: [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}

expect(multiPoint1).toBePointGeometry()
expect(multiPoint2).toBePointGeometry()

expect(multiPoint3).not.toBePointGeometry()
expect([22, -34.549, 22]).not.toBePointGeometry()
expect({coordinates: [100.0, 0.0]}).not.toBePointGeometry()

Passing Tests

This matcher does not test coordinate validity. Either two or three digit coordinates are allowed.

Values in Range

An array of two, three, and mixed dimension points. Input:

  • 2D: [[100.0, 0.0]], [[100.0, 0.0], [100.0, 0.0]], [[100.0, 0.0], [100.0, 0.0], [100.0, 0.0]]
  • 3D: [[100.0, 0.0, 0]], [[100.0, 0.0, 0], [100.0, 0.0, 0]], [[100.0, 0.0, 0], [100.0, 0.0, 0], [100.0, 0.0, 0]]
  • Mixed: [[100.0, 0.0, 0]], [[100.0, 0.0], [100.0, 0.0, 0]], [[100.0, 0.0, 0], [100.0, 0.0], [100.0, 0.0, 0]]
const testMultiPoint = {
    type: "MultiPoint",
    coordinates: <input>
}
  • Empty Coordinate
const testMultiPoint = {
    type: "MultiPoint",
    coordinates: [ ]
}

Foreign Properties Allowed

  • ID (String and number input: 'Test 1', 1
const testMultiPoint = {
    type: "MultiPoint",
    id: <input>,
    coordinates: [[25, 90], [-180, 0]]
}
  • Non alphanumeric ID
    ID Normally needs to be a letter or number, but because it is a foreign member on a geometry object, the GeoJSON standard takes no consideration about it.
const testMultiPoint1 = {
    type: "MultiPoint",
    id: null,
    coordinates: [[25, 90], [-180, 0]]
}
  • Geometries
const testMultiPoint2 = {
    type: "MultiPoint",
    geometries: testMultiPoint1,
    coordinates: [[-100.0, -15.0, 2000], [0, 0]]
}
  • Multiple
const testMultiPoint3 = {
    type: "MultiPoint",
    someRandomProp: true,
    geometries: testMultiPoint2,
    coordinates: [[180, 10.2, -125]]
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testMultiPoint = {
    type: 'MultiPoint',
    coordinates: [[0, 0, 0]],
    bbox: <input>
}
  • Illogical
const testMultiPoint = {
    type: 'MultiPoint',
    bbox: [-30.0, -30.0, -20.0, -20.0],
    coordinates: [[0, 0, 0]]
}
  • Redundant
const testMultiPoint = {
    type: 'MultiPoint',
    bbox: [0, 0, 0, 0],
    coordinates: [[0, 0]]
}

Failing Tests

Geometry Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays: [coordinates: [[0, 0], [1, 1]] }]
  • Strings: '', 'Random Geometry', '[0, 0]', '[[0, 0], [0, 0]]'
  • Stringified JSON:
    JSON.stringify({
        type: "MultiPoint",
        coordinates: [[25, 90], [2, 2]]
    })

Invalid Coordinates

  • Each of the values from "Geometry Input Not an Object":
const testMultiPoint = {
    type: "MultiPoint",
    coordinates: <input>
}

Coordinates Out of Range

This only tests a handful of invalid coordinates to verify behavior. Detailed coordinate validation occurs in isValidCoordinate

Input: [[0, 0], [181, 91]], [[0, 0], [181, -91]], [[0, 0], [-181, 91, 0]], [[0, 0], [-181, -91, 200]], [[0, 0, 0, 0]]

const testMultiPoint = {
    type: "MultiPoint",
    coordinates: <input>
}

Type Value Incorrect

Input:

  • Each of the values from "Geometry Input Not an Object", plus
  • 'Point', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', and 'GeometryCollection', or
  • 'MULTIPOINT', 'multipoint'
const testMultiPoint = {
    type: <input>,
    coordinates: [[0, 0], [1, 1, 0]]
}

Contains Prohibited Properties

Geometry

const testMultiPoint = {
    type: "MultiPoint",
    coordinates: [[0, 0], [1, 1, 0]],
    geometry: {
        type: "Point",
        coordinates: [102.0, 0.5]
    }
}

Properties

const testMultiPoint = {
    type: "MultiPoint",
    coordinates: [[0, 0], [1, 1, 0]],
    properties: {
        prop1: true
    }
}

Features

const testMultiPoint = {
    type: "MultiPoint",
    coordinates: [[0, 0], [1, 1, 0]],
    features: [{
        type: "Feature",
        geometry: {
            type: "Point",
            coordinates: [102.0, 0.5]
            },
        properties: {
           prop0: "value0"
        }
    }]
}

Missing Required Properties

  • No Type
const testMultiPoint = {
    coordinates: [[0, 0], [1, 1, 0]]
}
  • No Coordinates
const testMultiPoint = {
    type: "MultiPoint"
}

Invalid Bounding Box

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testMultiPoint = {
    type: 'MultiPoint',
    coordinates: [[0, 0]],
    bbox: <input>
}

isValidCoordinate

Description

This matcher follows the same rules as isValid2DCoordinate and isValid3DCoordinate. This particular matcher takes an argument of either a 2D or 3D coordinate and checks its validity.

It takes as an argument of an individual array value (i.e. not the entire coordinates property).

Valid GeoJSON Coordinate Examples

A two-element array of points:

{
   "type": "Feature",
   "geometry": {
       "type": "Point",
       "coordinates": [102.0, 0.5]
   },
   "properties": {
       "prop0": "value0"
   }
}

An array of coordinates:

{
   "type": "Feature",
   "geometry": {
       "type": "LineString",
       "coordinates": [
           [102.0, 0.0],
           [103.0, 1.0, -100],
           [104.0, 0.0, 0],
           [105.0, 1.0, 100]
       ]
   }
}

In each case, the matcher should only get passed the value of a single coordinate, not the object itself.

Example Matcher Usage

expect([22, -34.549]).isValidCoordinate()
expect([22, -34.549, 1000]).isValidCoordinate()

expect([322, -34.549, 0]).not.isValidCoordinate() // Longitude out of range
expect({coordinates: [22, -34.549, 22]}).not.isValidCoordinate() // Coordinates object instead of array

Repeat Tests Contained in Other Files

This matcher works with either 2D or 3D coordinates. Therefore, the passing and failing tests for isValid2DCoordinate and isValid3DCoordinate can also include tests for isValidCoordinate.

Unique Tests

  • Incorrect number of array elements (because 2D and 3D have conflicting tests for the combined matcher)
    • [ ], [ 20 ], [20, 30, 0, 4], [20, 30, 0, 20, 30, 0, 20, 30, 0]
  • Snapshot Testing

toHaveGeometryCount

Description

A GeoJSON GeometryCollection contains a "geometries" member with an array of GeoJSON geometry objects.

This matcher uses the toBeGeometryCollection functionality to verify the input is a properly formatted GeometryCollection object, and then determine if it has between Range1 and Range2 number of geometry objects in its "geometries". It will throw an error for any of the other geometry types as it is a trivial comparison on those.

Omitting Range2 or setting it equal to the value of Range1 will causes the matcher to check for exactly the number of geometries specified by Range1.

Checking Range2 less than Range1 will throw an error.

Passing a number less than 0 for either will throw an error.

Decimals will get truncated on both Range1 and Range2.

Will throw an error if Range2 is defined and Range1 is not.

If omitting both Range1 and Range2, it passes if at least one geometry object is contained in "geometries".

Example Matcher Usage

const testCollection = {
    "type": "GeometryCollection",
    "geometries": [{
        "type": "Point",
        "coordinates": [100.0, 0.0]
    }, {
        "type": "LineString",
        "coordinates": [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }, {
        "type": "Polygon",
        "coordinates": [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ]
    }, {
        "type": "Point",
        "coordinates": [150.0, 73.0]
    }]
}
const emptyCollection = {
    "type": "GeometryCollection",
    "geometries": []
}
const polygon = {
    type: 'Polygon',
    coordinates: [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]
    ]
}

expect(testCollection).toHaveGeometryCount(1, 8)
expect(testCollection).toHaveGeometryCount(4)

expect(testCollection).not.toHaveGeometryCount(5, 15)
expect(testCollection).not.toHaveGeometryCount(2, 3.99)
expect(testCollection).not.toHaveGeometryCount()
expect(emptyCollection).not.toHaveGeometryCount()
expect(polygon).not.toHaveGeometryCount(1)

Passing Tests

Good GeometryCollection

  • Known good GeometryCollection with a single geometry
    • .toHaveGeometryCount()
    • .toHaveGeometryCount(undefined)
    • .toHaveGeometryCount(1, undefined)
    • .toHaveGeometryCount(undefined, undefined)
    • .toHaveGeometryCount(1)
    • .toHaveGeometryCount(1, 1)
    • .toHaveGeometryCount(1.2)
    • .toHaveGeometryCount(1.2, 2)
    • .toHaveGeometryCount(1, 2)
    • .toHaveGeometryCount(0, 20)
  • Known good GeometryCollection with 4 geometries
    • .toHaveGeometryCount()
    • .toHaveGeometryCount(4)
    • .toHaveGeometryCount(4, 4)
    • .toHaveGeometryCount(4, undefined)
    • .toHaveGeometryCount(3.999, 4)
    • .toHaveGeometryCount(4.999, 8)
    • .toHaveGeometryCount(2, 4)
    • .toHaveGeometryCount(2, 15)
    • .toHaveGeometryCount(0, 20)
  • Stress test: Known good GeometryCollection with 100 geometries
    • .toHaveGeometryCount()
    • .toHaveGeometryCount(100)
    • .toHaveGeometryCount(100, 100)
    • .toHaveGeometryCount(49, 100000)
    • .toHaveGeometryCount(0, Infinity)
  • Empty Geometry
    • .toHaveGeometryCount(0)
    • .toHaveGeometryCount(0.1)
    • .toHaveGeometryCount(0.9)
    • .toHaveGeometryCount(0.9, 1)
    • .toHaveGeometryCount(0, 10)
    • .toHaveGeometryCount(0, undefined)
    • .toHaveGeometryCount(0, 0)

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects except GeometryCollection
  • An invalid GeometryCollection
  • Feature and FeatureCollection object
  • undefined, null, false, true, 0, NaN
  • { someProp: 'I am not GeoJSON', id: 4 }
  • {}
  • '',
  • 'Random Feature',
  •   JSON.stringify({
          "type": "GeometryCollection",
          "geometries": [{
              "type": "Point",
              "coordinates": [100.0, 0.0]
          }, {
              "type": "LineString",
              "coordinates": [
                  [101.0, 0.0],
                  [102.0, 1.0]
              ]
          }, {
              "type": "Polygon",
              "coordinates": [
                  [
                      [102.0, 2.0],
                      [103.0, 2.0],
                      [103.0, 3.0],
                      [102.0, 3.0],
                      [102.0, 2.0]
                  ]
              ]
          }, {
              "type": "Point",
              "coordinates": [150.0, 73.0]
          }]
      })

Valid GeometryCollection With Range1 and Range2 Problems

  • Good GeometryCollection with valid Range2 less than valid Range1
  • Good GeometryCollection with valid Range1 undefined and valid Range2
  • Good GeometryCollection with Range1 as each of the values in "Invalid Inputs To Matcher" except 0
  • Good GeometryCollection with Range2 as each of the values in "Invalid Inputs To Matcher" except 0
  • Good GeometryCollection with Range1 and Range2 as each of the values in "Invalid Inputs To Matcher" except 0

Valid GeometryCollection With Negative Ranges

  • Known good GeometryCollection with a single geometry
    • .not.toHaveGeometryCount(-1)
    • .not.toHaveGeometryCount(-1, undefined)
    • .not.toHaveGeometryCount(-10)
    • .not.toHaveGeometryCount(-Infinity)
  • Known good GeometryCollection with 4 geometries
    • .not.toHaveGeometryCount(-4)
    • .not.toHaveGeometryCount(-4, undefined)
    • .not.toHaveGeometryCount(-1, 5)
    • .not.toHaveGeometryCount(-2, -1)
    • .not.toHaveGeometryCount(-5, -1)
  • Empty Geometry
    • .not.toHaveGeometryCount(-1)
    • .not.toHaveGeometryCount(-1, 0)
    • .not.toHaveGeometryCount(-1, undefined)
    • .not.toHaveGeometryCount(-1, 2)
    • .not.toHaveGeometryCount(-2, -1)
    • .not.toHaveGeometryCount(-1, -2)
  • Nested Geometry Collection does not count nested geometries, only top level

Valid GeometryCollection With Out of Range Range1 and Range2

  • Known good GeometryCollection with a single geometry
    • .not.toHaveGeometryCount(0)
    • .not.toHaveGeometryCount(2)
  • Known good GeometryCollection with 4 geometries
    • .not.toHaveGeometryCount(0)
    • .not.toHaveGeometryCount(2)
    • .not.toHaveGeometryCount(3.9999)
    • .not.toHaveGeometryCount(0, 3)
    • .not.toHaveGeometryCount(5, 10)
    • .not.toHaveGeometryCount(0, undefined)
  • Stress test: Known good GeometryCollection with 100 geometries
    • .not.toHaveGeometryCount(0)
    • .not.toHaveGeometryCount(50)
    • .not.toHaveGeometryCount(99.5)
    • .not.toHaveGeometryCount(5, 10)
    • .not.toHaveGeometryCount(90, 99.999)
    • .not.toHaveGeometryCount(101, 1000)
    • .not.toHaveGeometryCount(0, undefined)
    • .not.toHaveGeometryCount(87, undefined)
  • Empty Geometry
    • .not.toHaveGeometryCount(1)
    • .not.toHaveGeometryCount(1, undefined)
    • .not.toHaveGeometryCount()
    • .not.toHaveGeometryCount(undefined)
    • .not.toHaveGeometryCount(1, 5)

toHaveMaxGeometryCount

Description

A GeoJSON GeometryCollection contains a "geometries" member with an array of GeoJSON geometry objects.

This matcher uses the toBeGeometryCollection functionality to verify the input is a properly formatted GeometryCollection object, and then determine if it has less than or equal to the MaxCount value. It will throw an error for any of the other geometry types as it is a trivial comparison on those.

Omitting MaxCount will assume a maximum value of 1. Passing a number less than 0 will throw an error. Decimals will get truncated.

Example Matcher Usage

const testCollection = {
    "type": "GeometryCollection",
    "geometries": [{
        "type": "Point",
        "coordinates": [100.0, 0.0]
    }, {
        "type": "LineString",
        "coordinates": [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }, {
        "type": "Polygon",
        "coordinates": [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ]
    }, {
        "type": "Point",
        "coordinates": [150.0, 73.0]
    }]
}
const emptyCollection = {
    "type": "GeometryCollection",
    "geometries": []
}
const polygon = {
    type: 'Polygon',
    coordinates: [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]
    ]
}

expect(testCollection).toHaveMaxGeometryCount(4)
expect(testCollection).toHaveMaxGeometryCount(22)
expect(emptyCollection).toHaveMaxGeometryCount()
expect(emptyCollection).toHaveMaxGeometryCount(0)

expect(testCollection).not.toHaveMaxGeometryCount(3.99)
expect(polygon).not.toHaveMaxGeometryCount(1)

Passing Tests

Good GeometryCollection

  • Known good GeometryCollection with a single geometry
    • .toHaveMaxGeometryCount()
    • .toHaveMaxGeometryCount(1)
    • .toHaveMaxGeometryCount(1.2)
  • Known good GeometryCollection with 4 geometries
    • .toHaveMaxGeometryCount(4)
    • .toHaveMaxGeometryCount(4.999)
    • .toHaveMaxGeometryCount(6)
    • .toHaveMaxGeometryCount(15)
  • Stress test: Known good GeometryCollection with 100 geometries
    • .toHaveMaxGeometryCount(100)
    • .toHaveMaxGeometryCount(100000)
    • .toHaveMaxGeometryCount(Infinity)
  • Empty Geometry
    • .toHaveMaxGeometryCount()
    • .toHaveMaxGeometryCount(1)
    • .toHaveMaxGeometryCount(0)
    • .toHaveMaxGeometryCount(0.1)
    • .toHaveMaxGeometryCount(0.9)

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects except GeometryCollection
  • An invalid GeometryCollection
  • Feature and FeatureCollection object
  • undefined, null, false, true, 0, NaN
  • { someProp: 'I am not GeoJSON', id: 4 }
  • {}
  • '',
  • 'Random Feature',
  •   JSON.stringify({
          "type": "GeometryCollection",
          "geometries": [{
              "type": "Point",
              "coordinates": [100.0, 0.0]
          }, {
              "type": "LineString",
              "coordinates": [
                  [101.0, 0.0],
                  [102.0, 1.0]
              ]
          }, {
              "type": "Polygon",
              "coordinates": [
                  [
                      [102.0, 2.0],
                      [103.0, 2.0],
                      [103.0, 3.0],
                      [102.0, 3.0],
                      [102.0, 2.0]
                  ]
              ]
          }, {
              "type": "Point",
              "coordinates": [150.0, 73.0]
          }]
      })

Valid GeometryCollection With Bad MinCount

  • Good GeometryCollection with MinCount as each of the values in "Invalid Inputs To Matcher" except 0

Valid GeometryCollection With Out of Range MinCount

  • Known good GeometryCollection with a single geometry
    • .not.toHaveMaxGeometryCount(0)
    • .not.toHaveMaxGeometryCount(-1)
    • .not.toHaveMaxGeometryCount(2)
  • Known good GeometryCollection with 4 geometries
    • .not.toHaveMaxGeometryCount()
    • .not.toHaveMaxGeometryCount(-10)
    • .not.toHaveMaxGeometryCount(2)
    • .not.toHaveMaxGeometryCount(3.9999)
  • Stress test: Known good GeometryCollection with 100 geometries
    • .not.toHaveMaxGeometryCount()
    • .not.toHaveMaxGeometryCount(-Infinity)
    • .not.toHaveMaxGeometryCount(50)
    • .not.toHaveMaxGeometryCount(99.5)
  • Empty Geometry
    • .not.toHaveMaxGeometryCount(-1)

toContainNumericIDs

Description

GeoJSON Features have an optional ID member that can be any string or number. This matcher verifies that all Features within a FeatureCollection contain IDs and that they are all type number.

If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.2

An "id" member on a non-feature object gets treated as a foreign member instead of an ID. Therefore, this matcher rejects non-FeatureCollection objects.

This test will fail if any of the IDs have a type string, do not have an ID, or if the "features" member is an empty array.

Example Matcher Usage

const goodTtestCollection = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 1
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 2
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 3
    }]
}
const badTestCollection1 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: "Test 1"
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 2
    }]
}

const badTestCollection2 = {
    type: "FeatureCollection",
    features: [{
        type: "Feature",
        geometry: {...},
        properties: {...},
        id: 11
    }, {
        type: "Feature",
        geometry: {...},
        properties: {...},
    }]
}

expect(goodTtestCollection).toContainNumericIDs()

expect(badTestCollection1).not.toContainNumericIDs()
expect(badTestCollection2).not.toContainNumericIDs()
expect(goodTtestCollection.features[0]).not.toContainNumericIDs()

Passing Tests

Unique Numeric IDs

  • FeatureCollection with Single Feature with ID
  • FeatureCollection with 5 Features and unique IDs
  • Stress Test: FeatureCollection with 100 Features and unique IDs

Identical Numeric IDs

  • FeatureCollection with 5 Features and some repeated IDs
  • FeatureCollection with 5 Features and all repeated IDs

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects
  • FeatureCollection object
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • '',
  • 'Random Feature',
  • JSON.stringify({
          type: 'FeatureCollection',
          features: []
      })

Only Some Have Numeric IDs, the Rest Null or Not Mentioned

  • FeatureCollection with 5 Features, 1 has no "id" member
  • FeatureCollection with 5 Features, 1 has an "id" member with null

FeatureCollection with Empty Features Array

Should fail because there are no features to check IDs on.

FeatureCollection with No ID

Should fail with a single feature that has no ID.

All Have String IDs

  • FeatureCollection with Single Feature and ID
  • FeatureCollection with 5 Features and unique IDs
  • FeatureCollection with 5 Features and repeated IDs

Only Some Have String IDs, the Rest Numeric ID, Null, or Not Mentioned

  • FeatureCollection with 5 Features
  • FeatureCollection with 100 Features

toBeMultiPolygonWithHole

Description

GeoJSON MultiPolygon geometries support multiple linear rings in their coordinates array following the same rules as those in polygons. Each ring after the first represents a hole.

  • A linear ring is the boundary of a surface or the boundary of a hole in a surface.
  • A linear ring MUST follow the right-hand rule with respect to the area it bounds, i.e., exterior rings are counterclockwise, and holes are clockwise.

Note: the [GJ2008] specification did not discuss linear ring winding order. For backwards compatibility, parsers SHOULD NOT reject Polygons that do not follow the right-hand rule.

Though a linear ring is not explicitly represented as a GeoJSON geometry type, it leads to a canonical formulation of the Polygon geometry type definition as follows:

  • For type "Polygon", the "coordinates" member MUST be an array of linear ring coordinate arrays.

For Polygons with more than one of these rings, the first MUST be the exterior ring, and any others MUST be interior rings. The exterior ring bounds the surface, and the interior rings (if present) bound holes within the surface.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6

This matcher will make no assumptions about winding order. A planned future matcher will check that. Per the standard, as long as it meets the above criteria, it is a valid polygon object. It will validate the geometry using the toBeMultiPolygonGeometry functionality.

Example Matcher Usage

const multiPolygon1 = {
    type: 'MultiPolygon',
    coordinates: [
        [
            [
                [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.2, 0.8],
                [100.8, 0.8],
                [100.8, 0.2],
                [100.2, 0.2]
            ]
        ]
    ]
}
const multiPolygon2 = {
    type: 'MultiPolygon',
    coordinates: [
        [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ]
    ]
}
const polygon = {
    type: 'Polygon',
    coordinates: [
        [
            [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.2, 0.8],
            [100.8, 0.8],
            [100.8, 0.2],
            [100.2, 0.2]
        ]
    ]
}

expect(multiPolygon1).toBeMultiPolygonWithHole()

expect(multiPolygon2).not.toBeMultiPolygonWithHole()
expect(polygon).not.toBeMultiPolygonWithHole()

Passing Tests

Good MultiPolygons

Test each for clockwise and counterclockwise:

  • Known good MultiPolygon with a single hole
  • Known good MultiPolygon with 2 holes
  • Stress test: Known good MultiPolygon with 30 polygons each with 30 holes

Illogical Holes

  • Hole has 0 area
  • Hole is outside geometry
  • Hole is wound counterclockwise

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry objects except MultiPolygon
  • An invalid MultiPolygon
  • Feature and FeatureCollection object
  • undefined, null, false, true, 0, NaN
  • { someProp: 'I am not GeoJSON', id: 4 }
  • {}
  • '',
  • 'Random Feature',
  •   JSON.stringify({
          type: 'Polygon',
          coordinates: [
              [
                  [
                      [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.2, 0.8],
                      [100.8, 0.8],
                      [100.8, 0.2],
                      [100.2, 0.2]
                  ]
              ]
          ]
      })

Non-Passing Polygons

  • MultiPolygon with bad geometry
  • MultiPolygon with no hole

toHave2DBoundingBox

Description

Bounding boxes are an optional GeoJSON property that describe a box of longitude boundaries that run along meridians and latitude boundaries that are parallel to the equator. A 2D Bounding Box only describes longitude and latitude boundaries, whereas a 3D Bounding Box describes an additional min and max altitude/depth value.

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections. The value of the bbox member MUST be an array of length 2*n where n is the number of dimensions represented in the contained geometries, with all axes of the most southwesterly point followed by all axes of the more northeasterly point. The axes order of a bbox follows the axes order of geometries.

The "bbox" values define shapes with edges that follow lines of constant longitude, latitude, and elevation.
~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

This matcher evaluates any Geometry, GeometryCollection, Feature, or FeatureCollection object to ensure it has a valid 2D bounding box. It will not check any subsets of these objects (i.e. it will not check all "geometries" in the collection also have 2D bounding boxes).

It takes an optional argument of a valid bounding box array, bboxEquals. If present, the feature must have a bounding box that exactly equals this value.

Example Matcher Usage

const testCollection = {
    type: "FeatureCollection",
    bbox: [-100.0, -10, 10, 49.5],
    features: [
        {
            type: "Feature",
            geometry: {...}
        },
        {
            type: "Feature",
            bbox: [-20, -5, 0, 0, 5, 200]
            geometry: {...}
        }
    ]
}
const testPolygon = {
    type: "Polygon",
    bbox: [-10, -10, 10, 10],
    coordinates: [
        [
            [-10.0, -10.0],
            [10.0, -10.0],
            [10.0, 10.0],
            [-10.0, -10.0]
        ]
    ]
}

expect(testCollection).toHave2DBoundingBox()
expect(testCollection).toHave2DBoundingBox([-100.0, -10, 10, 49.5])
expect(testPolygon).toHave2DBoundingBox()


expect(testCollection).not.toHave2DBoundingBox([-10, -10, 10, 10])
expect(testCollection.features[0]).not.toHave2DBoundingBox()
expect(testCollection.features[1]).not.toHave2DBoundingBox()
expect(testPolygon.bbox).not.toHave2DBoundingBox()

Passing Tests

Valid GeoJSON Objects with a BBox

Check with and without optional bboxEquals

  • Each of the seven Geometry, Feature, and FeatureCollection valid objects

Valid GeoJSON Objects with a BBox

A GeometryCollection, Feature, and FeatureCollection that has a BBox, but

  • one or more components do not
  • one or more have a 3D bounding box

Failing Tests

Invalid Inputs To Matcher

Rejects each of the following:

  • Each of the seven Geometry, Feature, and FeatureCollection objects when invalid
  • Arrays: [], [0, 0], [0, 0, 0], [[-20], [10], [-10], [20]], [0, 0, 0, 0], [0, 0, 0, 0, 0, 0]
  • undefined, null, false, true, 0
  • { someProp: 'I am not GeoJSON', id: 4 }
  • {}
  • '',
  • 'Random Feature',
  • JSON.stringify({
        type: 'Feature',
        geometry: null,
        properties: null
    })

Valid GeoJSON Objects without a BBox

Each of the seven Geometry, Feature, and FeatureCollection valid objects

Valid GeoJSON Objects with Component BBox but no Overall BBox

A GeometryCollection, Feature, and FeatureCollection that has no BBox, but the geometries do

Valid GeoJSON Objects with a BBox, bboxEquals Does Not Match

Each of the seven Geometry, Feature, and FeatureCollection valid objects

Incorrect number of array elements in bboxEquals

All items in "Invalid Inputs To Matcher" except the 6 element array

isValid2DBoundingBox

Description

Bounding boxes are an optional GeoJSON property that describe a box of longitude boundaries that run along meridians and latitude boundaries that are parallel to the equator. A 2D Bounding Box only describes longitude and latitude boundaries, whereas a 3D BBox describes an additional min and max altitude/depth value.

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections. The value of the bbox member MUST be an array of length 2*n where n is the number of dimensions represented in the contained geometries, with all axes of the most southwesterly point followed by all axes of the more northeasterly point. The axes order of a bbox follows the axes order of geometries.

The "bbox" values define shapes with edges that follow lines of constant longitude, latitude, and elevation.
~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Format

The values of a "bbox" array are [west, south, east, north], not [minx, miny, maxx, maxy].
~https://datatracker.ietf.org/doc/html/rfc7946#appendix-B.1

The Antimeridian

In the case of objects spanning the antimeridian, a bounding box becomes required.

Consider a set of point Features within the Fiji archipelago, straddling the antimeridian between 16 degrees S and 20 degrees S. The southwest corner of the box containing these Features is at 20 degrees S and 177 degrees E, and the northwest corner is at 16 degrees S and 178 degrees W. The antimeridian-spanning GeoJSON bounding box for this FeatureCollection is

"bbox": [177.0, -20.0, -178.0, -16.0]

and covers 5 degrees of longitude. The complementary bounding box for the same latitude band, not crossing the antimeridian, is

"bbox": [-178.0, -20.0, 177.0, -16.0]

and covers 355 degrees of longitude.

The latitude of the northeast corner is always greater than the latitude of the southwest corner, but bounding boxes that cross the antimeridian have a northeast corner longitude that is less than the longitude of the southwest corner.
~https://datatracker.ietf.org/doc/html/rfc7946#section-5.2

The Poles

An area that encompasses either the north or south pole will also require a bounding box that meets special use cases.

A bounding box that contains the North Pole extends from a southwest corner of "minlat" degrees N, 180 degrees W to a northeast corner of 90 degrees N, 180 degrees E. Viewed on a globe, this bounding box approximates a spherical cap bounded by the "minlat" circle of latitude.

"bbox": [-180.0, minlat, 180.0, 90.0]

A bounding box that contains the South Pole extends from a southwest corner of 90 degrees S, 180 degrees W to a northeast corner of "maxlat" degrees S, 180 degrees E.

"bbox": [-180.0, -90.0, 180.0, maxlat]

A bounding box that just touches the North Pole and forms a slice of an approximate spherical cap when viewed on a globe extends from a southwest corner of "minlat" degrees N and "westlon" degrees E to a northeast corner of 90 degrees N and "eastlon" degrees E.

"bbox": [westlon, minlat, eastlon, 90.0]

Similarly, a bounding box that just touches the South Pole and forms a slice of an approximate spherical cap when viewed on a globe has the following representation in GeoJSON.

"bbox": [westlon, -90.0, eastlon, maxlat]

Implementers MUST NOT use latitude values greater than 90 or less than -90 to imply an extent that is not a spherical cap.
~https://datatracker.ietf.org/doc/html/rfc7946#section-5.3

Boundaries

Any values outside of the WGS-84 standards should fail.

WGS84 Bounds: -180.0000, -90.0000, 180.0000, 90.0000
Projected Bounds: -180.0000, -90.0000, 180.0000, 90.0000
~ https://spatialreference.org/ref/epsg/wgs-84/
Also, see https://datatracker.ietf.org/doc/html/rfc7946#section-4

Although not explicitly stated, it can be inferred that the north value must be larger than the south value.

Valid GeoJSON Examples

Example of a 2D bbox member on a Feature:

{
    "type": "Feature",
    "bbox": [-10.0, -10.0, 10.0, 10.0],
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [-10.0, -10.0],
                [10.0, -10.0],
                [10.0, 10.0],
                [-10.0, -10.0]
            ]
        ]
    }
}

Example of a 2D bbox member on a FeatureCollection:

{
    "type": "FeatureCollection",
    "bbox": [100.0, 0.0, 105.0, 1.0],
         "features": [
             ...
         ]
}

Example of a 2D bbox on a feature with a FeatureCollection that also has a bounding box:

{
    "type": "FeatureCollection",
    "bbox": [-100.0, -10, 10, 49.5],
         "features": [
             {
                 "type": "Feature",
                 "bbox": [-10.0, -10.0, 10.0, 10.0],
                 "geometry": {
                     ...
                 }
             },
             ...
         ]
}

Example of a 2D bbox that crosses the antimeridian:

{
    "type": "FeatureCollection",
    "bbox": [177.0, -20.0, -178.0, -16.0],
         "features": [
             ...
         ]
}

Example Matcher Usage

expect([-10.0, -10.0, 10.0, 10.0]).isValid2DBoundingBox()
expect([100.0, 0.0, 105.0, 1.0]).isValid2DBoundingBox()
expect([177.0, -20.0, -178.0, -16.0]).isValid2DBoundingBox() // Crosses antimeridian and spans 5 degrees

expect([-10.0, -10.0, 10.0, 100.0]).not.isValid2DBoundingBox() // North out of bounds
expect([100.0, 0.0, -100.0, 105.0, 1.0, 0.0]).not.isValid2DCoordinate() // 3D bounding box
expect({ bbox: [-10.0, -10.0, 10.0, 10.0] }).not.isValid2DCoordinate() // Object instead of bbox array

Passing Tests

Values in Range

  • Each quadrant
    • NW: [-20, 10, -10, 20]
    • NE: [10, 10, 20, 20]
    • SW: [-20, -20, -10, -10]
    • SE: [10, -20, 20, -10]
  • Spanning all quadrants
    • Prime meridian: [-10, -20, 20, 10]
    • Antimeridian: [170, -20, -170, 20]

Values at Edge Cases

  • Longitude Boundaries:
    • West: [-180, 10, 20, 20]
    • North: [10, 10, 180, 20]
  • Poles with Cap
    • North: [-180.0, 80, 180.0, 90.0]
    • South: [-180.0, -90, 180.0, -80.0]
  • Poles not Including Cap
    • North: [10, 80, 20, 90.0]
    • South: [-45, -90, -80, -80.0]
  • Spanning all longitudes
    • All longitudes: [-180, 10, 180, 20]
    • All latitudes: [-10, -90, 10, 90]
    • Whole Earth: [-180, -90, 180, 90]
  • Northern Boundary Equals Southern:
    • [-10, -20, 10, -20]
    • [-10, 20, 10, 20]
  • Eastern Boundary Equals Western
    • [-10, -20, -10, 20]
    • [10, -20, 10, 20]
  • All zeros
    • [0, 0, 0, 0]

Failing Tests

Bbox input not an array

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Objects: { bbox: [10, 10, 20, 20] }
  • Strings: '', 'Random Coordinate', '[10, 10, 20, 20]'

Incorrect number of array elements

  • [ ], [20], [20, 10], [20, 30, 0], [20, 30, 0, 0, 10], [20, 30, 0, 20, 30, 0], [20, 30, 0, 20, 30, 0, 20, 30, 0]

Coordinates out of range

  • Latitude: [-10, -90.0000001, 10, 0], [-10, 0, 10, 90.0000001], [-10, -90000, 10, 0], [-10, 0, 10, 90000]
  • Longitude: [-180.0000001, -10, -160, 10], [160, -10, 180.0000001, 10], [-1800000, -10, -160, 10], [160, -10, 1800000, 10]
  • Combination:
    • [-181, -10, 181, 10]
    • [-10, -91, 10, 91]
    • [-181, -91, 10, 10]
    • [-10, -10, 181, 91]
    • [-181, -91, 181, 91]

Illogical BBox

  • Northern Boundary Less Than Southern: [-10, 20, 10, -20]

BBox has non-numeric values

  • Each of the values in "Bbox input not an array" paired with valid inputs:
    • [<value>, -10, 10, 10]
    • [-10, <value>, 10, 10]
    • [-10, -10, <value>, 10]
    • [-10, -10, 10, <value>]
    • [<value>, <value>, <value>, <value>]

BBox values are arrays of numbers

[[-20], [10], [-10], [20]]

Matcher Tests Should Have Comprehensive Snapshots

While refactoring several core geometry functions, I realized that although the tests were still passing, I was now seeing less than helpful error messages for tests designed to fail.

Adding an additional snapshot to capture all of each function's error messages will help further refactoring to ensure that any changes still result in helpful error messages.

toBeValidGeoJSON

Description

This is the most generic matcher, and expected to be one of the most commonly used ones.

This matcher takes as an argument of any GeoJSON object and checks if it meets validity requirements for any of the nine types:

It does not accept individual components of these objects.

See the above links for examples.

Example Matcher Usage

point = {
    "type": "Point",
    "coordinates": [100.0, 0.0]
}
lineString = {
    "type": "LineString",
    "coordinates": [
        [
            [180.0, 40.0],
            [180.0, 50.0],
            [170.0, 50.0],
            [170.0, 40.0],
            [180.0, 40.0]
        ]
    ]
}
polygon = {
    "type": "Polygon",
    "coordinates": [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]
    ]
}
feature = {
    "type": "Feature",
    "geometry": {
        "type": "Point",
        "coordinates": [102.0, 0.5]
    }
}
geometryCollection = {
    "type": "GeometryCollection",
    "geometries": [{
        "type": "Point",
        "coordinates": [100.0, 0.0]
    }, {
        "type": "LineString",
        "coordinates": [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }, {
        "type": "Polygon",
        "coordinates": [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ]
    }, {
        "type": "Point",
        "coordinates": [150.0, 73.0]
    }]
}

expect(point).toBeValidGeoJSON()
expect(lineString).toBeValidGeoJSON()
expect(polygon).toBeValidGeoJSON()
expect(feature).toBeValidGeoJSON()
expect(feature.geometry).toBeValidGeoJSON()
expect(geometryCollection).toBeValidGeoJSON()
expect(geometryCollection.geometries[1]).toBeValidGeoJSON()

expect(polygon.coordinates).not.toBeValidGeoJSON()
expect(geometryCollection.geometries).toBeValidGeoJSON()
expect([322, -34.549, 0]).not.toBeValidGeoJSON()
expect({coordinates: [22, -34.549, 22]}).not.toBeValidGeoJSON()

Repeat Passing/Failing Tests Contained in Other Files

This matcher works with any geometry, Feature, or FeatureCollection. Therefore, the passing and failing tests for each of the nine can also include tests for toBeValidGeoJSON.

Unique Tests

Input Not an Object

-undefined
-null
-Booleans: true, false
-Numbers: 200, -200, Infinity, -Infinity, NaN
-Arrays: [25, 35, 45000], [[0, 0], [1, 1]], [ ]
-Strings: '', 'Random Geometry', '[0, 0]', '[[0, 0], [0, 0]]'

  • Stringified JSON:
    JSON.stringify({
        type: "LineString",
        coordinates: [[25, 90], [2, 2]]
    })

Non-Object GeoJSON Components

  • Rejects Coordinates
  • Rejects Bounding Boxes
  • Rejects array of geometries

Other

  • Snapshots

toBeLineStringGeometry

Description

This is a building block matcher. Geometries are the basis of all objects, and we need a way to reliably test that they have been formatted appropriately.

This matcher focuses only on "LineString". All coordinates must be valid coordinates per the WGS-84 standard. Any non-conforming values should fail. However, the "coordinates" property MAY have an empty array ([ ]) as a valid value. It MAY NOT have an array of empty arrays as a value ([ [ ], [ ] ]).

A Geometry object represents points, curves, and surfaces in coordinate space. Every Geometry object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • The value of a Geometry object's "type" member MUST be one of the seven geometry types (per Section 1.4: "Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", and "GeometryCollection").
  • A GeoJSON Geometry object of any type other than "GeometryCollection" has a member with the name "coordinates". The value of the "coordinates" member is an array. The structure of the elements in this array is determined by the type of geometry. GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.

~https://datatracker.ietf.org/doc/html/rfc7946#section-3.1

For type "LineString", the "coordinates" member is an array of two or more positions.
https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.4

The geometry object should have a "type" and a "coordinates" property. It is prohibited from having a "geometry", "properties", or "features" property.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.
https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

Although illogical, it is NOT PROHIBITED from having a "geometries" property, although if present it would be considered a foreign member.

Other foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members should get checked for validity of any type; they may contain anything that is valid JSON.

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Bounding boxes, if present, must validate (see isValidBoundingBox).

A GeoJSON object represents a Geometry, Feature, or collection of Features.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Valid GeoJSON LineString Examples

Multiple Points

    {
        "type": "LineString",
        "coordinates": [
            [
                [180.0, 40.0], [180.0, 50.0], [170.0, 50.0],
                [170.0, 40.0], [180.0, 40.0]
            ]
        ]
    }

An empty coordinate

{
    "type": "LineString",
    "coordinates": [ ]
}

Contained as an object within a geometries array on a GeometryCollection:

{
    "type": "GeometryCollection",
    "geometries": [{
        "type": "LineString",
        "coordinates": [
            [
                [180.0, 40.0], [180.0, 50.0], [170.0, 50.0],
                [170.0, 40.0], [180.0, 40.0], [180.0, 40.0]
            ]
        ]
    }, {
        "type": "MultiPoint",
        "coordinates": [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }]
}

In each case, the matcher should only get passed the object in its entirety, not the individual components, and not as part of another collection object.

Example Matcher Usage

const lineString = {
    "type": "LineString",
    "coordinates": [
        [
            [180.0, 40.0], [180.0, 50.0], [170.0, 50.0],
            [170.0, 40.0], [180.0, 40.0]
        ]
    ]
}
const multiPoint = {
    type: "MultiPoint",
    coordinates: [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}

expect(lineString).toBeLineStringGeometry()

expect(multiPoint).not.toBeLineStringGeometry()
expect({coordinates: [100.0, 0.0]}).not.toBeLineStringGeometry()

Passing Tests

This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.

Values in Range

An array of two, three, and mixed dimension points. Input:

  • 2D: [[0, 1], [0, 2]], [[1, 0], [2, 0], [3, 0]]
  • 3D: [[2, 20, 0], [4, 10, 0]], [[3, 0.0, 0], [6, -10, 0], [9, -20, 0]]
  • Mixed: [[100.0, 0.0], [90, 0.0, 0]], [[100.0, 0.0, 0], [110, 5], [100.0, 11.33, 259]]
  • Ring: [[180.0, 40.0], [180.0, 50.0], [170.0, 50.0], [170.0, 40.0], [180.0, 40.0]]
  • Antimeridian: [[175, 0], [-175, 0]], [[-175, 0], [175, 0]]
    • Note that both of these are only implied to cross the antimeridian due to proximity. The actual standard assumes these would span 350 degrees. Either way, they are valid coordinate arrays.
  • Repeated and Redundant: [[0, 0], [0, 0], [0, 0]]
  • Stressed: An array of 30 points
const testLineString = {
    type: "LineString",
    coordinates: <input>
}
  • Empty Coordinate
const testLineString = {
    type: "LineString",
    coordinates: [ ]
}

Foreign Properties Allowed

  • ID (String and number input: 'Test 1', 1
const testLineString = {
    type: "LineString",
    id: <input>,
    coordinates: [[25, 90], [-180, 0]]
}
  • Non alphanumeric ID
    ID Normally needs to be a letter or number, but because it is a foreign member on a geometry object, the GeoJSON standard takes no consideration about it.
const testLineString = {
    type: "LineString",
    id: null,
    coordinates: [[25, 90], [-180, 0]]
}
  • Geometries
const testLineString2 = {
    type: "LineString",
    geometries: testLineString1,
    coordinates: [[-100.0, -15.0, 2000], [0, 0]]
}
  • Multiple
const testLineString3 = {
    type: "LineString",
    someRandomProp: true,
    geometries: testLineString2,
    coordinates: [[180, 10.2, -125], [-180, 90, 35000]]
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testLineString = {
    type: 'LineString',
    coordinates: [[0, 0], [-5, -5]],
    bbox: <input>
}
  • Illogical
const testLineString = {
    type: 'LineString',
    coordinates: [[0, 0], [-5, -5]],
    bbox: [-30.0, -30.0, -20.0, -20.0]
}
  • Redundant
const testLineString = {
    type: 'LineString',
    coordinates: [[0, 0], [-5, -5]],
    bbox: [0, 0, 0, 0]
}

Failing Tests

Geometry Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays: [coordinates: [[0, 0], [1, 1]]], [ ]
  • Strings: '', 'Random Geometry', '[0, 0]', '[[0, 0], [0, 0]]'
  • Stringified JSON:
    JSON.stringify({
        type: "LineString",
        coordinates: [[25, 90], [2, 2]]
    })

Invalid Coordinates

  • Each of the values from "Geometry Input Not an Object":
const testLineString= {
    type: "LineString",
    coordinates: [[0, 0], <input>]
}

Coordinates Out of Range

This only tests a handful of invalid coordinates to verify behavior. Detailed coordinate validation occurs in isValidCoordinate

Input: [[0, 0], [181, 91]], [[0, 0], [181, -91]], [[0, 0], [-181, 91, 0]], [[0, 0], [-181, -91, 200]], [[0, 0, 0, 0]]

const testLineString= {
    type: "LineString",
    coordinates: <input>
}

Single Coordinate

  • Coordinates array must have two or more positions.
const testLineString= {
    type: "LineString",
    coordinates: [[0, 0]]
}

Type Value Incorrect

Input:

  • Each of the values from "Geometry Input Not an Object", plus
  • 'Point', 'MultiPoint', 'MultiLineString', 'Polygon', 'MultiPolygon', and 'GeometryCollection', or
  • 'LINESTRING', 'linestring'
const testLineString = {
    type: <input>,
    coordinates: [[0, 0], [1, 1, 0]]
}

Contains Prohibited Properties

Geometry

const testLineString = {
    type: "LineString",
    coordinates: [[0, 0], [1, 1, 0]],
    geometry: {
        type: "Point",
        coordinates: [102.0, 0.5]
    }
}

Properties

const testLineString = {
    type: "LineString",
    coordinates: [[0, 0], [1, 1, 0]],
    properties: {
        prop1: true
    }
}

Features

const testLineString = {
    type: "LineString",
    coordinates: [[0, 0], [1, 1, 0]],
    features: [{
        type: "Feature",
        geometry: {
            type: "Point",
            coordinates: [102.0, 0.5]
            },
        properties: {
           prop0: "value0"
        }
    }]
}

Missing Required Properties

  • No Type
const testLineString = {
    coordinates: [[0, 0], [1, 1, 0]]
}
  • No Coordinates
const testLineString = {
    type: "LineString"
}

Coordinates is an Array of Null Arrays

  • Test for 1x, 2x, 3x
  • No Type
const testLineString = {
    type: "LineString",
    coordinates: [<arrays>]
}

Invalid Bounding Box

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testPoint = {
    type: 'LineString',
    coordinates: [[0, 0], [1, 1, 0]],
    bbox: <input>
}

Create an Enhancement Issue Form

There are other ways the project's functionality can get improved besides just adding new matchers. For example,

  • Add new core function without associated matcher
  • Expand scope of an existing matcher
  • Improve build tooling

There should be an issue template for this kind of change request as well.

[Docs Request]: Add advanced configuration instructions

Component

README

Description

Advanced configuration currently includes importing the core functions, but it still needs to address the following:

  • How to install with Yarn
  • Setting up individual matcher groups only
  • Setting up custom a custom matcher importing and loading script

See https://jestjs.io/docs/configuration for more configuration details.

Are you able/willing to make the change? (It's ok if you're not!)

Yes

Code of Conduct

The automated release is failing 🚨

🚨 The automated release from the beta branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you can benefit from your bug fixes and new features again.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can fix this πŸ’ͺ.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the beta branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here are some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


Cannot push to the Git repository.

semantic-release cannot push the version tag to the branch beta on the remote Git repository with URL https://x-access-token:[secure]@github.com/M-Scott-Lassiter/jest-geojson.git.

This can be caused by:


Good luck with your project ✨

Your semantic-release bot πŸ“¦πŸš€

Matchers Need Basic API Documentation

Component

Matchers

Description

The matchers have JSDoc comments, but no publicized documentation showing end users how the API works.

This should not get cluttered up on the main README, but should be an automated process to reduce the workload.

Are you able/willing to make the change? (It's ok if you're not!)

Yes

Code of Conduct

toBePointGeometry

Description

This is a building block matcher. Geometries are the basis of all objects, and we need a way to reliably test that they have been formatted appropriately.

This matcher focuses only on "Point". All coordinates must be valid coordinates per the WGS-84 standard. Any non-conforming values should fail. However, the "coordinates" property MAY have an empty array ([ ]) as a valid value.

A Geometry object represents points, curves, and surfaces in coordinate space. Every Geometry object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • The value of a Geometry object's "type" member MUST be one of the seven geometry types (per Section 1.4: "Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", and "GeometryCollection").
  • A GeoJSON Geometry object of any type other than "GeometryCollection" has a member with the name "coordinates". The value of the "coordinates" member is an array. The structure of the elements in this array is determined by the type of geometry. GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.

~https://datatracker.ietf.org/doc/html/rfc7946#section-3.1

For type "Point", the "coordinates" member is a single position.
https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.2

The geometry object should have a "type" and a "coordinates" property. It is prohibited from having a "geometry", "properties", or "features" property.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.
https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

Although illogical, it is NOT PROHIBITED from having a "geometries" property, although if present it would be considered a foreign member.

Other foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members should get checked for validity of any type; they may contain anything that is valid JSON.

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Bounding boxes, if present, must validate (see isValidBoundingBox).

A GeoJSON object represents a Geometry, Feature, or collection of Features.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3

A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-5

Valid GeoJSON Point Geometry Examples

A two dimensional of point:

{
    "type": "Point",
    "coordinates": [100.0, 0.0]
}

A three dimensional of point:

{
    "type": "Point",
    "coordinates": [100.0, 0.0, 2000]
}

An empty coordinate

{
    "type": "Point",
    "coordinates": [ ]
}

Contained as an object within a geometries array on a GeometryCollection:

{
    "type": "GeometryCollection",
    "geometries": [{
        "type": "Point",
        "coordinates": [100.0, 0.0]
    }, {
        "type": "LineString",
        "coordinates": [
            [101.0, 0.0],
            [102.0, 1.0]
        ]
    }]
}

In each case, the matcher should only get passed the object in its entirety, not the individual components, and not as part of another collection object.

Example Matcher Usage

const point1 = {
    type: "Point",
    coordinates: [100.0, 0.0]
}
const point2 = {
    type: "Point",
    coordinates: [100.0, 0.0, 2000]
}
const point3 = {
    type: "LineString",
    coordinates: [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}

expect(point1).toBePointGeometry()
expect(point2).toBePointGeometry()

expect(point3).not.toBePointGeometry()
expect([22, -34.549, 22]).not.toBePointGeometry()
expect({coordinates: [100.0, 0.0]}).not.toBePointGeometry()

Passing Tests

This matcher does not test coordinate validity. Either two or three digit coordinates are allowed.

Values in Range

  • 2D Point
const testPoint = {
    type: "Point",
    coordinates: [25, 10.2]
}
  • 3D Point
const testPoint = {
    type: "Point",
    coordinates: [-100.0, -15.0, 2000]
}
  • Empty Coordinate
const testPoint = {
    type: "Point",
    coordinates: [ ]
}

Foreign Properties Allowed

  • ID (String and number input: Test 1, 1
const testPoint = {
    type: "Point",
    id: <input>,
    coordinates: [25, 90]
}
  • Non alphanumeric ID
    ID Normally needs to be a letter or number, but because it is a foreign member on a geometry object, the GeoJSON standard takes no consideration about it.
const testPoint1 = {
    type: "Point",
    id: null,
    coordinates: [25, 90]
}
  • Geometries
const testPoint2 = {
    type: "Point",
    geometries: testPoint1,
    coordinates: [-100.0, -15.0, 2000]
}
  • Multiple
const testPoint3 = {
    type: "Point",
    someRandomProp: true,
    geometries: testPoint2,
    coordinates: [180, 10.2, -125]
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testPoint = {
    type: 'Point',
    coordinates: [0, 0],
    bbox: <input>
}
  • Illogical
const testPoint = {
    type: 'Point',
    bbox: [-30.0, -30.0, -20.0, -20.0],
    coordinates: [0, 0]
}
  • Redundant
const testPoint = {
    type: 'Point',
    bbox: [0, 0, 0, 0],
    coordinates: [0, 0]
}

Failing Tests

Geometry Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays: [{ coordinates: [0, 0] }]
  • Strings: '', 'Random Geometry', '[0, 0]', '[[0, 0], [0, 0]]'
  • Stringified JSON:
    JSON.stringify({
        type: "Point",
        coordinates: [25, 90]
    })

Invalid Coordinates

  • Each of the values from "Geometry Input Not an Object":
const testPoint = {
    type: "Point",
    coordinates: <input>
}

Coordinates Out of Range

This only tests a handful of invalid coordinates to verify behavior. Detailed coordinate validation occurs in isValidCoordinate

Input: [181, 91], [181, -91], [-181, 91, 0], [-181, -91, 200], [0, 0, 0, 0]

const testPoint = {
    type: "Point",
    coordinates: <input>
}

Type Value Incorrect

Input:

  • Each of the values from "Geometry Input Not an Object", plus
  • 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', and 'GeometryCollection', or
  • 'POINT', 'point'
const testPoint = {
    type: <input>,
    coordinates: [0, 0]
}

Contains Prohibited Properties

Geometry

const testPoint = {
    type: "Point",
    coordinates: [0, 0],
    geometry: {
        type: "Point",
        coordinates: [102.0, 0.5]
    }
}

Properties

const testPoint = {
    type: "Point",
    coordinates: [0, 0],
    properties: {
        prop1: true
    }
}

Features

const testPoint = {
    type: "Point",
    coordinates: [0, 0],
    features: [{
        type: "Feature",
        geometry: {
            type: "Point",
            coordinates: [102.0, 0.5]
            },
        properties: {
           prop0: "value0"
        }
    }]
}

Missing Required Properties

  • No Type
const testPoint = {
    coordinates: [0, 0]
}
  • No Coordinates
const testPoint = {
    type: "Point"
}

Invalid Bounding Box

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testPoint = {
    type: 'Point',
    coordinates: [0, 0],
    bbox: <input>
}

[Bug]: Missing coordinate as foreign member test for toBeGeometryCollection

What happened?

The spec has a different formulation for GeometryCollection objects. Most geometries require a "coordinates" member, but GeometryCollection requires a "geometries" member instead.

There is no accounting for this in the current matcher.

The spec does not prohibit "coordinates", although it means nothing to a GeometryCollection and should be treated as any other foreign member. Any value should get accepted.

  • Update issue #16
  • Add new test criteria

Version

1.0.0-beta.14

What operating system are you seeing the problem on?

Windows

What version of Node are you seeing the problem on?

16

Relevant log output

No response

Are you able/willing to make the change? (It's ok if you're not!)

Yes

Code of Conduct

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.