m-scott-lassiter / jest-geojson Goto Github PK
View Code? Open in Web Editor NEWGeoJSON Validation Matchers for Jest
Home Page: https://m-scott-lassiter.github.io/jest-geojson/
License: MIT License
GeoJSON Validation Matchers for Jest
Home Page: https://m-scott-lassiter.github.io/jest-geojson/
License: MIT License
Merged into toBeFeature.
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.
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.
{
"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)
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
Input: 'Test ID', 3
const testFeature = {
type: 'Feature',
id: 'Test ID',
geometry: null,
properties: null
}
expect(feature).toHaveStringID([])
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
}
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/]
Rejects each of the following:
undefined
, null
, false
, true
, 0
{ someProp: 'I am not GeoJSON', id: 4 }
''
,'Random Feature',
JSON.stringify({
type: 'Feature',
geometry: null,
properties: null
})
const testFeature = {
type: 'Feature',
geometry: null,
properties: null
}
Invalid features should throw the toBeFeature errors instead of new ones here.
Rejects when the optional ID to check is
undefined
, null
, false
, true
{ someProp: 'I am not GeoJSON', id: 4 }
{}
NaN
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.
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.
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)
For both Strings, Numbers, and Combinations:
For both Strings, Numbers, and Combinations:
null
null
null
.null
.Rejects each of the following:
undefined
, null
, false
, true
, 0
{ someProp: 'I am not GeoJSON', id: 4 }
''
,'Random Feature'
,JSON.stringify({
type: 'FeatureCollection',
features: []
})
For both Strings, Numbers, and Combinations:
For both Strings, Numbers, and Combinations:
null
Should fail because there are no features to check IDs on.
null
.null
.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.
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()
This matcher works with any geometry. Therefore, the passing and failing tests for each of the seven can also include tests for toBeAnyGeometry
.
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.
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.
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()
For both Strings, Numbers, and Combinations:
For both Strings, Numbers, and Combinations:
Rejects each of the following:
undefined
, null
, false
, true
, 0
{ someProp: 'I am not GeoJSON', id: 4 }
''
,'Random Feature'
,JSON.stringify({
type: 'FeatureCollection',
features: []
})
For both Strings, Numbers, and Combinations:
null
Should fail because there are no features to check IDs on.
null
.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.
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.
{
"type": "Feature",
"id": 123,
"geometry": {...},
"properties": {...}
}
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')
Input:
1
0
5000
-Infinity
Infinity
const testFeature = {
type: 'Feature',
id: <input>,
geometry: null,
properties: null
}
const testFeature = {
type: 'Feature',
id: 719,
geometry: null,
properties: null
}
expect(feature).toHaveNumericID([])
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]
Rejects each of the following:
undefined
, null
, false
, true
, 0
{ someProp: 'I am not GeoJSON', id: 4 }
''
,'Random Feature'
,JSON.stringify({
type: 'Feature',
geometry: null,
properties: null
})
Input:
'1'
'Test 123'
'Random ID String'
''
const testFeature = {
type: 'Feature',
id: <input>,
geometry: null,
properties: null
}
Rejects when the optional ID to check is
'Some String'
, ''
, '1'
undefined
, null
, false
, true
{ someProp: 'I am not GeoJSON', id: 4 }
{}
NaN
CONTRIBUTING
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.
Yes
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.
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)
Rejects each of the following:
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]
}]
})
MinCount
as each of the values in "Invalid Inputs To Matcher" except 0This 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).
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.
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
This matcher works with either 2D or 3D bounding boxes. Therefore, the passing and failing tests for isValid2DBoundingBox
and isValid3DBoundingBox
can also include tests for isValidBoundingBox
.
[ ]
, [ 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]
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.
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.
A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.
{
"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.
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()
This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.
const testGeometryCollection = {
type: "GeometryCollection",
geometries: [ ]
}
'Test 1'
, 1
, null
const testGeometryCollection = {
type: "GeometryCollection",
id: <input>,
geometries: ...
}
const testGeometryCollection = {
type: "GeometryCollection",
someRandomProp: true,
geometries: [{
type: "Point",
coordinates: [5, 15]
}],
otherProp: [5, 15]
}
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]
}]
}
[-10.0, -10.0, 10.0, 10.0]
[-10.0, -10.0, 0, 10.0, 10.0, 200]
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [{
"type": "Point",
"coordinates": [0, 0]
}],
bbox: <input>
}
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [{
"type": "Point",
"coordinates": [0, 0]
}],
bbox: [-30.0, -30.0, -20.0, -20.0]
}
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [{
"type": "Point",
"coordinates": [0, 0]
}],
bbox: [0, 0, 0, 0]
}
[0, 0]
, false
, {}
, 2
, 'Coordinate String'
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [{
"type": "Point",
"coordinates": [0, 0]
}],
coordinates: <input>
}
Any of the following inputs to the matcher should fail:
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
[1, 1]
''
, 'Random Geometry'
, '[0, 0]'
JSON.stringify({
"type": "GeometryCollection",
"geometries": [{
"type": "Point",
"coordinates": [100.0, 0.0]
}]
})
Should fail when "geometries":
Input:
'Point'
, 'MultiPoint'
, 'LineString'
, 'MultiLineString'
, 'Polygon'
, and 'MultiPolygon'
, or'GEOMETRYCOLLECTION'
, 'geometrycollection'
{
type: <input>,
geometries: [{
"type": "Point",
"coordinates": [-180, 90, 2000]
}]
}
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"
}
}]
}
const testGeometryCollection = {
geometries: [{
"type": "Point",
"coordinates": [0, 0]
}]
}
const testGeometryCollection = {
type: "GeometryCollection"
}
[[]]
[[], []]
[{}, {}]
{}
const testGeometryCollection = {
type: "GeometryCollection",
geometries: <input>
}
null
undefined
[]
[-10.0, -10.0, 10.0]
[-10.0, -10.0, 190.0, 10.0]
[-10.0, 10.0, 10.0, -10]
[-10.0, -10.0, 0, 10, 10.0, '200']
const testGeometryCollection = {
type: 'GeometryCollection',
geometries: [
{
type: 'Point',
coordinates: [180, -90, 2000]
}
],
bbox: <input>
}
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.
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.
A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.
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.
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()
This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.
const testPolygon = {
type: "Polygon",
coordinates: [<input>]
}
const testPolygon = {
type: "Polygon",
coordinates: [ ]
}
'Test 1'
, 1
const testPolygon = {
type: "Polygon",
id: <input>,
coordinates: [[[25, 90], [-180, 0]]]
}
const testPolygon1 = {
type: "Polygon",
id: null,
coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
}
const testPolygon2 = {
type: "Polygon",
geometries: testPolygon1,
coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
}
const testPolygon3 = {
type: "Polygon",
someRandomProp: true,
geometries: testPolygon2,
coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
}
[-10.0, -10.0, 10.0, 10.0]
[-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>
}
const testPolygon = {
type: 'Polygon',
coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
bbox: [-30.0, -30.0, -20.0, -20.0]
}
const testPolygon = {
type: 'Polygon',
coordinates: [[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]],
bbox: [0, 0, 0, 0]
}
Any of the following inputs to the matcher should fail:
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
[1, 1]
[ ]
''
, 'Random Geometry'
, '[0, 0]'
, '[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]'
JSON.stringify({
type: "Polygon",
coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
})
const testPolygon= {
type: "Polygon",
coordinates: [[[0, 0], [0, 1], [1, 1], <input>, [0, 0]]]
}
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>]
}
Input:
[[[0, 0]]]
[[[0, 0], [1, 1]]]
[[[0, 0], [1, 1]], [[1, 0]]]
const testPolygon= {
type: "Polygon",
coordinates: <input>
}
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>]
}
Input:
'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>
}
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"
}
}]
}
const testPolygon = {
coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
}
const testPolygon = {
type: "Polygon"
}
const testPolygon = {
type: "Polygon",
coordinates: [[<arrays>]]
}
null
undefined
[]
[-10.0, -10.0, 10.0]
[-10.0, -10.0, 190.0, 10.0]
[-10.0, 10.0, 10.0, -10]
[-10.0, -10.0, 0, 10, 10.0, '200']
const testPolygon = {
type: 'Polygon',
coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
bbox: <input>
}
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.
{
"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 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()
Test each for clockwise and counterclockwise:
Rejects each of the following:
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]
]
]
})
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.
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.
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()
Rejects each of the following:
undefined
, null
, false
, true
, 0
{ someProp: 'I am not GeoJSON', id: 4 }
''
,'Random Feature'
,JSON.stringify({
type: 'FeatureCollection',
features: []
})
null
Should fail because there are no features to check IDs on.
Should fail with a single feature that has no ID.
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.
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.
A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.
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.
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()
This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.
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]]]
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: [ ]
}
'Test 1'
, 1
const testMultiLineString = {
type: "MultiLineString",
id: <input>,
coordinates: [[[25, 90], [-180, 0]]]
}
const testMultiLineString1 = {
type: "MultiLineString",
id: null,
coordinates: [[[25, 90], [-180, 0]]]
}
const testMultiLineString2 = {
type: "MultiLineString",
geometries: testMultiLineString1,
coordinates: [[[-100.0, -15.0, 2000], [0, 0]]]
}
const testMultiLineString3 = {
type: "MultiLineString",
someRandomProp: true,
geometries: testMultiLineString2,
coordinates: [[[180, 10.2, -125], [-180, 90, 35000]]]
}
[-10.0, -10.0, 10.0, 10.0]
[-10.0, -10.0, 0, 10.0, 10.0, 200]
const testMultiLineString = {
type: 'MultiLineString',
coordinates: [[[0, 0], [-5, -5]]],
bbox: <input>
}
const testMultiLineString = {
type: 'MultiLineString',
coordinates: [[[0, 0], [-5, -5]]],
bbox: [-30.0, -30.0, -20.0, -20.0]
}
const testMultiLineString = {
type: 'MultiLineString',
coordinates: [[[0, 0], [-5, -5]]],
bbox: [0, 0, 0, 0]
}
Any of the following inputs to the matcher should fail:
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
[[[180, 10.2, -125]
, [-180, 90, 35000]]]
, [ ]
''
, 'Random Geometry'
, '[0, 0]'
, '[[[0, 0], [0, 0]]]'
JSON.stringify({
type: "MultiLineString",
coordinates: [[[25, 90], [2, 2]]]
})
const testMultiLineString= {
type: "MultiLineString",
coordinates: [[[0, 0], <input>]]
}
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>
}
const testMultiLineString= {
type: "MultiLineString",
coordinates: [[[0, 0]]]
}
Input:
'Point'
, 'MultiPoint'
, 'LineString'
, 'Polygon'
, 'MultiPolygon'
, and 'GeometryCollection'
, or'MULTILINESTRING'
, 'multilinestring'
const testMultiLineString = {
type: <input>,
coordinates: [[[0, 0], [1, 1, 0]]]
}
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"
}
}]
}
const testMultiLineString = {
coordinates: [[[0, 0], [1, 1, 0]]]
}
const testMultiLineString = {
type: "MultiLineString"
}
const testMultiLineString = {
type: "MultiLineString",
coordinates: [[<arrays>]]
}
null
undefined
[]
[-10.0, -10.0, 10.0]
[-10.0, -10.0, 190.0, 10.0]
[-10.0, 10.0, 10.0, -10]
[-10.0, -10.0, 0, 10, 10.0, '200']
const testPoint = {
type: 'MultiLineString',
coordinates: [[[0, 0], [-5, -5]]],
bbox: <input>
}
Merged into toBeFeature.
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.
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.
{
"type": "Feature",
"id": "f1",
"geometry": {...},
"properties": {...}
}
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)
Input:
'1'
'Test 123'
'Random ID String'
''
const testFeature = {
type: 'Feature',
id: <input>,
geometry: null,
properties: null
}
const testFeature = {
type: 'Feature',
id: 'Test ID',
geometry: null,
properties: null
}
expect(feature).toHaveStringID([])
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']
Rejects each of the following:
undefined
, null
, false
, true
, 0
{ someProp: 'I am not GeoJSON', id: 4 }
''
,'Random Feature'
,JSON.stringify({
type: 'Feature',
geometry: null,
properties: null
})
Input:
0
-200
200
Infinity
-Infinity
const testFeature = {
type: 'Feature',
id: <input>,
geometry: null,
properties: null
}
Rejects when the optional ID to check is
undefined
, null
, false
, true
{ someProp: 'I am not GeoJSON', id: 4 }
{}
NaN
Package Peer Dependency
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:
package.json
Yes
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.
'Feature'
null
null
or an empty object ({}
).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).
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.
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.
{
"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.
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')
This matcher should use core functions to test coordinate validity.
.toBeFeature()
).toBeFeature('Point')
)const testFeature = {
type: 'Feature',
geometry: null,
properties: {
prop1: 'Some Prop'
}
}
[-10.0, -10.0, 10.0, 10.0]
[-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
}
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
}
const testFeature = {
type: 'Feature',
bbox: [0, 0, 0, 0],
geometry: null,
properties: null
}
const testFeature = {
type: 'Feature',
geometry: null,
properties: null
}
const testFeature = {
type: 'Feature',
geometry: null,
properties: {}
}
'ABCD'
, 'Test 1'
, '1'
, ''
, '[[[180, 10.2, -125], [-180, 90, 35000]]], [{}]'
0
, 200
, -200
, Infinity
, -Infinity
,const testFeature = {
type: 'Feature',
id: <input>,
geometry: null,
properties: null
}
const testFeature = {
type: 'Feature',
geometry: null,
properties: null,
foreign: true
}
const testFeature = {
type: 'Feature',
id: '#1',
geometry: null,
properties: null,
foreign1: true,
foreign2: 33
}
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'
}
Any of the following inputs to the matcher should fail:
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
[25, 35, 45000]
, [ ]
''
, 'Random Feature'
JSON.stringify({
type: 'Feature',
geometry: null,
properties: null
})
.toBeFeature()
).toBeFeature('Point')
)Input:
'Point'
, 'MultiPoint'
, 'LineString'
, 'MultiLineString'
'Polygon'
, 'MultiPolygon'
, and 'GeometryCollection'
, or'FEATURE'
, 'feature'
const testFeature = {
type: <input>,
geometry: null,
properties: null
}
Input:
null
undefined
[]
[-10.0, -10.0, 10.0]
[-10.0, -10.0, 190.0, 10.0]
[-10.0, 10.0, 10.0, -10]
[-10.0, -10.0, 0, 10, 10.0, '200']
const testFeature = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [102.0, 0.5]
},
properties: null,
bbox: <input>
}
Input:
undefined
null
true
, false
NaN
[25, 35, 45000]
, [ ]
{prop: 1}
, { }
const testFeature = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [102.0, 0.5]
},
properties: null,
id: <input>
}
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
}
const testFeature = {
properties: null,
geometry: {
type: 'Point',
coordinates: [102.0, 0.5]
}
}
const testFeature = {
type: 'Feature',
properties: null
}
const testFeature = {
type: 'Feature',
geometry: null
}
null
, plusconst testFeature = {
type: 'Feature',
properties: null,
geometry: [{
type: 'Point',
coordinates: [102.0, 0.5]
}]
}
const testFeature = {
type: 'Feature',
properties: null,
geometry: [{
type: 'Point',
coordinates: [102.0, 0.5]
}, {
type: 'Point',
coordinates: [122.0, -10.25]
}]
}
null
Merged into toBeFeature.
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.)
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]
]
]
}
expect([22, -34.549, 0]).isValid3DCoordinate()
expect([22, -34.549]).not.isValid3DCoordinate()
expect({ coordinates: [22, -34.549, 22, 0] }).not.isValid3DCoordinate()
[0, 0, 0]
, [102.0, 0.5, 1000]
, [172.0, -15, -1000]
, [-10.9, 77, 5000]
, [-152.0, -33.33333, -5000]
[180, 0, Infinity]
, [-180, 0, Infinity]
, [0, 90, Infinity]
, [0,-90, Infinity]
, [180, 90, -Infinity]
, [180,-90, -Infinity]
, [-180, 90, -Infinity]
, [-180,-90, -Infinity]
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
{ coordinates: [0, 0, 0] }
''
, 'Random Coordinate'
, '[0, 0, 0]'
, '[[0, 0, 0], [0, 0, 0]]'
[ ]
, [ 20 ]
, [20, 30]
, [20, 30, 0, 20, 30, 0, 20, 30, 0]
[0, 90.0000001, 0]
, [0, -90.0000001, 0]
, [0, 900000, 0]
, [0, -900000, 0]
[180.0000001, 0, 0]
, [-180.0000001, 0, 0]
, [1800000, 0, 0]
, [-1800000, 0, 0]
[181, 91, 0]
, [181, -91, 0]
, [-181, 91, 0]
, [-181, -91, 0]
[<value>, 0, 0]
[0, <value>, 0]
[<value>, <value>, 0]
[0, 0, <value>]
undefined
null
true
, false
NaN
{ coordinates: [0, 0, 0] }
'Random Coordinate'
, '[0, 0, 0]'
, '[[0, 0, 0], [0, 0, 0]]'
[ [ [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] ] ] ] ]
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.
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()
Check with and without optional bboxEquals
A GeometryCollection, Feature, and FeatureCollection that has a BBox, but
Rejects each of the following:
[]
, [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
})
Each of the seven Geometry, Feature, and FeatureCollection valid objects
A GeometryCollection, Feature, and FeatureCollection that has no BBox, but the geometries do
Each of the seven Geometry, Feature, and FeatureCollection valid objects
All items in "Invalid Inputs To Matcher" except the 4 element array
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
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]
.
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
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
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.
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.
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": [
...
]
}
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
[-20, 10, 0, -10, 20, 0]
[10, 10, 0, 20, 20, 0]
[-20, -20, 0, -10, -10, 0]
[10, -20, 0, 20, -10, 0]
[-10, -20, 0, 20, 10, 0]
[170, -20, 0, 20, -170, 0]
[-10, -20, -100, 20, 10, 0]
, [-10, -20, -500, 20, 10, -50]
[170, -20, 0, -170, 20, 100]
, [-10, -20, 50, 20, 10, 500]
[-10, -20, -22.5, 20, 10, 12345.678]
[-180, 10, 0, 20, 20, 0]
[10, 10, 0, 180, 20, 0]
[-180.0, 80, 0, 180.0, 90.0, 0]
[-180.0, -90, 0, 180.0, -80.0, 0]
[10, 80, 0, 20, 90.0, 0]
[-45, -90, 0, -80, -80.0, 0]
[-180, 10, 0, 180, 20, 0]
[-10, -90, 0, 10, 90, 0]
[-180, -90, 0, 180, 90, 0]
[-180, -90, -100000, 180, 90, 100000]
[-10, -20, 0, 10, -20, 0]
[-10, 20, 0, 10, 20, 0]
[-10, -20, 0, -10, 20, 0]
[10, -20, 0, 10, 20, 0]
[-10, -20, -220, -10, 20, -220]
[-10, -20, 330, -10, 20, 330]
[0, 0, 0, 0, 0, 0]
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
{ bbox: [10, 10, 0, 20, 20, 0] }
''
, 'Random Coordinate'
, '[10, 10, 0, 20, 20, 0]'
[ ]
, [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]
[-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]
[-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]
[-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]
[-10, 20, 0, 10, -20, 0]
[-10, -20, 200, 20, 10, 150]
[<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>]
[[-20], [10], [0], [-10], [20], [0]]
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.
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:
v1.0.0-beta.13
Windows
16
None
Yes
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.
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.
expect([22, -34.549]).isValid2DCoordinate()
expect([22, -34.549, 22]).not.isValid2DCoordinate()
expect({coordinates: [22, -34.549, 22]}).not.isValid2DCoordinate()
[0, 0]
, [102.0, 0.5]
, [172.0, -15]
, [-10.9, 77]
, [-152.0, -33.33333]
[180, 0]
, [-180, 0]
, [0, 90]
, [0,-90]
, [180, 90]
, [180,-90]
, [-180, 90]
, [-180,-90]
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
{ coordinates: [0, 0] }
''
, 'Random Coordinate'
, '[0, 0]'
, '[[0, 0], [0, 0]]'
[ ]
, [ 20 ]
, [20, 30, 0]
, [20, 30, 0, 20, 30, 0, 20, 30, 0]
[0, 90.0000001]
, [0, -90.0000001]
, [0, 900000]
, [0, -900000]
[180.0000001, 0]
, [-180.0000001, 0]
, [1800000, 0]
, [-1800000, 0]
[181, 91]
, [181, -91]
, [-181, 91]
, [-181, -91]
[<value>, 0]
[0, <value>]
[<value>, <value>]
[ [ [1, 1], [0, 0] ] ]
[ [ [10, 20], [2, 59] ] ]
[ [ [10, 20], [2, 90], [95, 5] ] ]
[ [ [ [1, 1], [0, 0] ] ] ]
, [ [ [ [ [2, 2], [3, 3] ] ] ] ]
Merged into toBeFeature.
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.
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()
Check with and without optional bboxEquals
A GeometryCollection, Feature, and FeatureCollection that has a BBox, but one or more components do not
Rejects each of the following:
[]
, [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
})
Each of the seven Geometry, Feature, and FeatureCollection valid objects
A GeometryCollection, Feature, and FeatureCollection that has no BBox, but the geometries do
Each of the seven Geometry, Feature, and FeatureCollection valid objects
All items in "Invalid Inputs To Matcher" except the 4 and 6 element arrays
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
filepackage.json
"types" memberFor more information and references, see:
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.
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.
'FeatureCollection'
[]
).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.
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.
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.
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.
A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.
{
"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.
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')
This matcher should use core functions to test object validity.
const testFeatureCollection = {
type: 'FeatureCollection',
features: []
}
[-10.0, -10.0, 10.0, 10.0]
[-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>
}
const testFeatureCollection = {
type: 'FeatureCollection',
features: [{
type: "MultiPoint",
coordinates:[[0, 0], [1, 1]]
}],
bbox: [-30.0, -30.0, -20.0, -20.0]
}
const testFeatureCollection = {
type: 'FeatureCollection',
features: [{
type: "MultiPoint",
coordinates:[[0, 0], [0, 0]]
}],
bbox: [0, 0, 0, 0]
}
null
, undefined
true
, false
'ABCD'
, 'Test 1'
, '1'
, ''
, '[[[180, 10.2, -125], [-180, 90, 35000]]], [{}]'
0
, 200
, -200
, Infinity
, -Infinity
, NaN
[]
, [1]
{}
, {id: 1}
const testFeatureCollection = {
type: 'FeatureCollection',
id: <input>,
features: []
}
const testFeatureCollection = {
type: 'FeatureCollection',
features: [],
foreign: true
}
const testFeatureCollection = {
type: 'FeatureCollection',
features: [],
id: '#1',
foreign1: true,
foreign2: 33
}
const testFeatureCollection = {
type: 'FeatureCollection',
features: [],
foreignGeometry: multiPoint,
Geometry: multiPoint // Note captitalized 'G'
}
Any of the following inputs to the matcher should fail:
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
[25, 35, 45000]
, [ ]
''
, 'Random FeatureCollection'
JSON.stringify({
type: 'FeatureCollection',
features: [{
type: 'Feature',
properties: {},
geometry: {
type: "MultiPoint",
coordinates:[[0, 0], [1, 1, 100]]
}
}]
})
Input:
'Point'
, 'MultiPoint'
, 'LineString'
, 'MultiLineString'
'Polygon'
, 'MultiPolygon'
, and 'GeometryCollection'
, 'Feature'
, or'FEATURECOLLECTION'
, 'featurecollection'
const testFeatureCollection = {
type: <input>,
features: []
}
const testFeatureCollection = {
type: 'FeatureCollection',
features: [<input>]
}
const testFeatureCollection = {
type: 'FeatureCollection',
features: <input>
}
Input:
null
undefined
[]
[-10.0, -10.0, 10.0]
[-10.0, -10.0, 190.0, 10.0]
[-10.0, 10.0, 10.0, -10]
[-10.0, -10.0, 0, 10, 10.0, '200']
const testFeatureCollection = {
type: 'FeatureCollection',
features: [...],
bbox: <input>
}
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
}
}
const testFeatureCollection = {
features: []
}
const testFeatureCollection = {
type: 'FeatureCollection'
}
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.
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.
A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.
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.
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()
This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.
const testMultiPolygon = {
type: "MultiPolygon",
coordinates: [
[<input>]
]
}
const testMultiPolygon = {
type: "MultiPolygon",
coordinates: [ ]
}
'Test 1'
, 1
const testMultiPolygon = {
type: "MultiPolygon",
id: <input>,
coordinates: ...
}
const testMultiPolygon1 = {
type: "MultiPolygon",
id: null,
coordinates: [
[
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
]
]
}
const testMultiPolygon2 = {
type: "MultiPolygon",
geometries: testMultiPolygon1,
coordinates: [
[
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
]
]
}
const testMultiPolygon3 = {
type: "MultiPolygon",
someRandomProp: true,
geometries: testMultiPolygon2,
coordinates: [
[
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
]
]
}
[-10.0, -10.0, 10.0, 10.0]
[-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>
}
const testMultiPolygon = {
type: 'MultiPolygon',
coordinates: [
[
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
]
],
bbox: [-30.0, -30.0, -20.0, -20.0]
}
const testMultiPolygon = {
type: 'MultiPolygon',
coordinates: [
[
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
]
],
bbox: [0, 0, 0, 0]
}
Any of the following inputs to the matcher should fail:
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
[1, 1]
[ ]
''
, 'Random Geometry'
, '[0, 0]'
, '[[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]]'
JSON.stringify({
type: "MultiPolygon",
coordinates: [
[
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
]
]
})
const testMultiPolygon= {
type: "MultiPolygon",
coordinates: [
[
[[0, 0], [0, 1], [1, 1], <input>, [0, 0]]
]
]
}
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>]
]
}
Input:
[[[0, 0]]]
[[[0, 0], [1, 1]]]
[[[0, 0], [1, 1]], [[1, 0]]]
const testMultiPolygon= {
type: "MultiPolygon",
coordinates: [<input>]
}
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>]
]
}
Input:
'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>
}
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"
}
}]
}
const testMultiPolygon = {
coordinates: [
[
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
]
]
}
const testMultiPolygon = {
type: "MultiPolygon"
}
const testMultiPolygon = {
type: "MultiPolygon",
coordinates: [[[[<arrays>]]]]
}
null
undefined
[]
[-10.0, -10.0, 10.0]
[-10.0, -10.0, 190.0, 10.0]
[-10.0, 10.0, 10.0, -10]
[-10.0, -10.0, 0, 10, 10.0, '200']
const testMultiPolygon = {
type: 'MultiPolygon',
coordinates: [
[
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
]
],
bbox: <input>
}
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.
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.
A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.
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.
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()
This matcher does not test coordinate validity. Either two or three digit coordinates are allowed.
An array of two, three, and mixed dimension points. Input:
[[100.0, 0.0]]
, [[100.0, 0.0], [100.0, 0.0]]
, [[100.0, 0.0], [100.0, 0.0], [100.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], [100.0, 0.0, 0]]
[[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>
}
const testMultiPoint = {
type: "MultiPoint",
coordinates: [ ]
}
'Test 1'
, 1
const testMultiPoint = {
type: "MultiPoint",
id: <input>,
coordinates: [[25, 90], [-180, 0]]
}
const testMultiPoint1 = {
type: "MultiPoint",
id: null,
coordinates: [[25, 90], [-180, 0]]
}
const testMultiPoint2 = {
type: "MultiPoint",
geometries: testMultiPoint1,
coordinates: [[-100.0, -15.0, 2000], [0, 0]]
}
const testMultiPoint3 = {
type: "MultiPoint",
someRandomProp: true,
geometries: testMultiPoint2,
coordinates: [[180, 10.2, -125]]
}
[-10.0, -10.0, 10.0, 10.0]
[-10.0, -10.0, 0, 10.0, 10.0, 200]
const testMultiPoint = {
type: 'MultiPoint',
coordinates: [[0, 0, 0]],
bbox: <input>
}
const testMultiPoint = {
type: 'MultiPoint',
bbox: [-30.0, -30.0, -20.0, -20.0],
coordinates: [[0, 0, 0]]
}
const testMultiPoint = {
type: 'MultiPoint',
bbox: [0, 0, 0, 0],
coordinates: [[0, 0]]
}
Any of the following inputs to the matcher should fail:
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
[coordinates: [[0, 0], [1, 1]] }]
''
, 'Random Geometry'
, '[0, 0]'
, '[[0, 0], [0, 0]]'
JSON.stringify({
type: "MultiPoint",
coordinates: [[25, 90], [2, 2]]
})
const testMultiPoint = {
type: "MultiPoint",
coordinates: <input>
}
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>
}
Input:
'Point'
, 'LineString'
, 'MultiLineString'
, 'Polygon'
, 'MultiPolygon'
, and 'GeometryCollection'
, or'MULTIPOINT'
, 'multipoint'
const testMultiPoint = {
type: <input>,
coordinates: [[0, 0], [1, 1, 0]]
}
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"
}
}]
}
const testMultiPoint = {
coordinates: [[0, 0], [1, 1, 0]]
}
const testMultiPoint = {
type: "MultiPoint"
}
null
undefined
[]
[-10.0, -10.0, 10.0]
[-10.0, -10.0, 190.0, 10.0]
[-10.0, 10.0, 10.0, -10]
[-10.0, -10.0, 0, 10, 10.0, '200']
const testMultiPoint = {
type: 'MultiPoint',
coordinates: [[0, 0]],
bbox: <input>
}
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).
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.
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
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
.
[ ]
, [ 20 ]
, [20, 30, 0, 4]
, [20, 30, 0, 20, 30, 0, 20, 30, 0]
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".
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)
Rejects each of the following:
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]
}]
})
Range2
less than valid Range1
Range1
undefined and valid Range2
Range1
as each of the values in "Invalid Inputs To Matcher" except 0Range2
as each of the values in "Invalid Inputs To Matcher" except 0Range1
and Range2
as each of the values in "Invalid Inputs To Matcher" except 0A 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.
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)
Rejects each of the following:
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]
}]
})
MinCount
as each of the values in "Invalid Inputs To Matcher" except 0Merged into toBeFeature.
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.
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.
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()
Rejects each of the following:
undefined
, null
, false
, true
, 0
{ someProp: 'I am not GeoJSON', id: 4 }
''
,'Random Feature'
,JSON.stringify({
type: 'FeatureCollection',
features: []
})
null
Should fail because there are no features to check IDs on.
Should fail with a single feature that has no ID.
Merged into toBeFeature.
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.
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()
Test each for clockwise and counterclockwise:
Rejects each of the following:
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]
]
]
]
})
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.
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()
Check with and without optional bboxEquals
A GeometryCollection, Feature, and FeatureCollection that has a BBox, but
Rejects each of the following:
[]
, [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
})
Each of the seven Geometry, Feature, and FeatureCollection valid objects
A GeometryCollection, Feature, and FeatureCollection that has no BBox, but the geometries do
Each of the seven Geometry, Feature, and FeatureCollection valid objects
All items in "Invalid Inputs To Matcher" except the 6 element array
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
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
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
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
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.
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": [
...
]
}
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
[-20, 10, -10, 20]
[10, 10, 20, 20]
[-20, -20, -10, -10]
[10, -20, 20, -10]
[-10, -20, 20, 10]
[170, -20, -170, 20]
[-180, 10, 20, 20]
[10, 10, 180, 20]
[-180.0, 80, 180.0, 90.0]
[-180.0, -90, 180.0, -80.0]
[10, 80, 20, 90.0]
[-45, -90, -80, -80.0]
[-180, 10, 180, 20]
[-10, -90, 10, 90]
[-180, -90, 180, 90]
[-10, -20, 10, -20]
[-10, 20, 10, 20]
[-10, -20, -10, 20]
[10, -20, 10, 20]
[0, 0, 0, 0]
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
{ bbox: [10, 10, 20, 20] }
''
, 'Random Coordinate'
, '[10, 10, 20, 20]'
[ ]
, [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]
[-10, -90.0000001, 10, 0]
, [-10, 0, 10, 90.0000001]
, [-10, -90000, 10, 0]
, [-10, 0, 10, 90000]
[-180.0000001, -10, -160, 10]
, [160, -10, 180.0000001, 10]
, [-1800000, -10, -160, 10]
, [160, -10, 1800000, 10]
[-181, -10, 181, 10]
[-10, -91, 10, 91]
[-181, -91, 10, 10]
[-10, -10, 181, 91]
[-181, -91, 181, 91]
[-10, 20, 10, -20]
[<value>, -10, 10, 10]
[-10, <value>, 10, 10]
[-10, -10, <value>, 10]
[-10, -10, 10, <value>]
[<value>, <value>, <value>, <value>]
[[-20], [10], [-10], [20]]
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.
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.
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()
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
.
-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]]'
JSON.stringify({
type: "LineString",
coordinates: [[25, 90], [2, 2]]
})
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.
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.
A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.
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.
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()
This matcher should use core functions to test coordinate validity. Either two or three digit coordinates are allowed.
An array of two, three, and mixed dimension points. Input:
[[0, 1], [0, 2]]
, [[1, 0], [2, 0], [3, 0]]
[[2, 20, 0], [4, 10, 0]]
, [[3, 0.0, 0], [6, -10, 0], [9, -20, 0]]
[[100.0, 0.0], [90, 0.0, 0]]
, [[100.0, 0.0, 0], [110, 5], [100.0, 11.33, 259]]
[[180.0, 40.0], [180.0, 50.0], [170.0, 50.0], [170.0, 40.0], [180.0, 40.0]]
[[175, 0], [-175, 0]]
, [[-175, 0], [175, 0]]
[[0, 0], [0, 0], [0, 0]]
const testLineString = {
type: "LineString",
coordinates: <input>
}
const testLineString = {
type: "LineString",
coordinates: [ ]
}
'Test 1'
, 1
const testLineString = {
type: "LineString",
id: <input>,
coordinates: [[25, 90], [-180, 0]]
}
const testLineString = {
type: "LineString",
id: null,
coordinates: [[25, 90], [-180, 0]]
}
const testLineString2 = {
type: "LineString",
geometries: testLineString1,
coordinates: [[-100.0, -15.0, 2000], [0, 0]]
}
const testLineString3 = {
type: "LineString",
someRandomProp: true,
geometries: testLineString2,
coordinates: [[180, 10.2, -125], [-180, 90, 35000]]
}
[-10.0, -10.0, 10.0, 10.0]
[-10.0, -10.0, 0, 10.0, 10.0, 200]
const testLineString = {
type: 'LineString',
coordinates: [[0, 0], [-5, -5]],
bbox: <input>
}
const testLineString = {
type: 'LineString',
coordinates: [[0, 0], [-5, -5]],
bbox: [-30.0, -30.0, -20.0, -20.0]
}
const testLineString = {
type: 'LineString',
coordinates: [[0, 0], [-5, -5]],
bbox: [0, 0, 0, 0]
}
Any of the following inputs to the matcher should fail:
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
[coordinates: [[0, 0], [1, 1]]]
, [ ]
''
, 'Random Geometry'
, '[0, 0]'
, '[[0, 0], [0, 0]]'
JSON.stringify({
type: "LineString",
coordinates: [[25, 90], [2, 2]]
})
const testLineString= {
type: "LineString",
coordinates: [[0, 0], <input>]
}
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>
}
const testLineString= {
type: "LineString",
coordinates: [[0, 0]]
}
Input:
'Point'
, 'MultiPoint'
, 'MultiLineString'
, 'Polygon'
, 'MultiPolygon'
, and 'GeometryCollection'
, or'LINESTRING'
, 'linestring'
const testLineString = {
type: <input>,
coordinates: [[0, 0], [1, 1, 0]]
}
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"
}
}]
}
const testLineString = {
coordinates: [[0, 0], [1, 1, 0]]
}
const testLineString = {
type: "LineString"
}
const testLineString = {
type: "LineString",
coordinates: [<arrays>]
}
null
undefined
[]
[-10.0, -10.0, 10.0]
[-10.0, -10.0, 190.0, 10.0]
[-10.0, 10.0, 10.0, -10]
[-10.0, -10.0, 0, 10, 10.0, '200']
const testPoint = {
type: 'LineString',
coordinates: [[0, 0], [1, 1, 0]],
bbox: <input>
}
Merged into toBeFeature.
There are other ways the project's functionality can get improved besides just adding new matchers. For example,
There should be an issue template for this kind of change request as well.
README
Advanced configuration currently includes importing the core functions, but it still needs to address the following:
See https://jestjs.io/docs/configuration for more configuration details.
Yes
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.
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
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.
Yes
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.
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.
A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections.
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.
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()
This matcher does not test coordinate validity. Either two or three digit coordinates are allowed.
const testPoint = {
type: "Point",
coordinates: [25, 10.2]
}
const testPoint = {
type: "Point",
coordinates: [-100.0, -15.0, 2000]
}
const testPoint = {
type: "Point",
coordinates: [ ]
}
Test 1
, 1
const testPoint = {
type: "Point",
id: <input>,
coordinates: [25, 90]
}
const testPoint1 = {
type: "Point",
id: null,
coordinates: [25, 90]
}
const testPoint2 = {
type: "Point",
geometries: testPoint1,
coordinates: [-100.0, -15.0, 2000]
}
const testPoint3 = {
type: "Point",
someRandomProp: true,
geometries: testPoint2,
coordinates: [180, 10.2, -125]
}
[-10.0, -10.0, 10.0, 10.0]
[-10.0, -10.0, 0, 10.0, 10.0, 200]
const testPoint = {
type: 'Point',
coordinates: [0, 0],
bbox: <input>
}
const testPoint = {
type: 'Point',
bbox: [-30.0, -30.0, -20.0, -20.0],
coordinates: [0, 0]
}
const testPoint = {
type: 'Point',
bbox: [0, 0, 0, 0],
coordinates: [0, 0]
}
Any of the following inputs to the matcher should fail:
undefined
null
true
, false
200
, -200
, Infinity
, -Infinity
, NaN
[{ coordinates: [0, 0] }]
''
, 'Random Geometry'
, '[0, 0]'
, '[[0, 0], [0, 0]]'
JSON.stringify({
type: "Point",
coordinates: [25, 90]
})
const testPoint = {
type: "Point",
coordinates: <input>
}
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>
}
Input:
'MultiPoint'
, 'LineString'
, 'MultiLineString'
, 'Polygon'
, 'MultiPolygon'
, and 'GeometryCollection'
, or'POINT'
, 'point'
const testPoint = {
type: <input>,
coordinates: [0, 0]
}
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"
}
}]
}
const testPoint = {
coordinates: [0, 0]
}
const testPoint = {
type: "Point"
}
null
undefined
[]
[-10.0, -10.0, 10.0]
[-10.0, -10.0, 190.0, 10.0]
[-10.0, 10.0, 10.0, -10]
[-10.0, -10.0, 0, 10, 10.0, '200']
const testPoint = {
type: 'Point',
coordinates: [0, 0],
bbox: <input>
}
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.
1.0.0-beta.14
Windows
16
No response
Yes
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.