Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

toBeMultiPolygonGeometry #14

Closed
M-Scott-Lassiter opened this issue May 24, 2022 · 4 comments
Closed

toBeMultiPolygonGeometry #14

M-Scott-Lassiter opened this issue May 24, 2022 · 4 comments

Comments

@M-Scott-Lassiter
Copy link
Owner

M-Scott-Lassiter commented May 24, 2022

Description

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Valid GeoJSON MultiPolygon Examples

Two polygons, one without a hole and one with:

{
    "type": "MultiPolygon",
    "coordinates": [
        [
            [
                [102.0, 2.0],
                [103.0, 2.0],
                [103.0, 3.0],
                [102.0, 3.0],
                [102.0, 2.0]
            ]
        ],
        [
            [
                [100.0, 0.0],
                [101.0, 0.0],
                [101.0, 1.0],
                [100.0, 1.0],
                [100.0, 0.0]
            ],
            [
                [100.2, 0.2],
                [100.2, 0.8],
                [100.8, 0.8],
                [100.8, 0.2],
                [100.2, 0.2]
            ]
        ]
    ]
}

A single polygon:

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

Antimeridian Cutting:

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

An empty coordinates

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

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

Example Matcher Usage

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

expect(multiPolygon).toBeMultiPolygonGeometry()

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

Passing Tests

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

Values in Range

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

Foreign Properties Allowed

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

May Have Optional Bounding Box

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

Failing Tests

Geometry Input Not an Object

Any of the following inputs to the matcher should fail:

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

Invalid Coordinates

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

Coordinates Out of Range

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

Input:

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

Not Enough Coordinates

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

Input:

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

Final Coordinate does not Match First Coordinate

Input:

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

Type Value Incorrect

Input:

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

Input:

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

Contains Prohibited Properties

Geometry

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

Properties

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

Features

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

Missing Required Properties

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

Coordinates is an Array of Null Arrays

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

Invalid Bounding Box

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testMultiPolygon = {
    type: 'MultiPolygon',
    coordinates: [
        [
            [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
        ]
    ],
    bbox: <input>
}
@M-Scott-Lassiter M-Scott-Lassiter added the new matcher proposal Proposal for a new GeoJSON matcher label May 24, 2022
@M-Scott-Lassiter M-Scott-Lassiter added this to To do in Geometries via automation May 24, 2022
@M-Scott-Lassiter M-Scott-Lassiter self-assigned this May 24, 2022
@M-Scott-Lassiter M-Scott-Lassiter changed the title [New Matcher]: toBeMultiPolygonGeometry toBeMultiPolygonGeometry May 24, 2022
M-Scott-Lassiter added a commit that referenced this issue May 30, 2022
Verifies an object is a valid GeoJSON MultiPolygon Geometry.

Resolves: #14
github-actions bot pushed a commit that referenced this issue May 30, 2022
## [1.0.0-beta.11](v1.0.0-beta.10...v1.0.0-beta.11) (2022-05-30)

### 🎁 Feature Changes

* **toBeMultiPolygonGeometry:** add new matcher ([41fef3a](41fef3a)), closes [#14](#14)
@M-Scott-Lassiter
Copy link
Owner Author

🎉 This issue has been resolved in version 1.0.0-beta.11 🎉

The release is available on:

Your semantic-release bot 📦🚀

@M-Scott-Lassiter M-Scott-Lassiter added released on @beta matchers/geometries and removed new matcher proposal Proposal for a new GeoJSON matcher labels May 30, 2022
Geometries automation moved this from To do to Done May 30, 2022
@M-Scott-Lassiter
Copy link
Owner Author

Reopening to add coverage to validate optional bounding boxes.

Geometries automation moved this from Done to In progress May 31, 2022
M-Scott-Lassiter added a commit that referenced this issue Jun 1, 2022
…d matchers

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

Resolves: #9, #10, #11, #12, #13, #14, #16, #29
github-actions bot pushed a commit that referenced this issue Jun 1, 2022
## [1.0.0-beta.14](v1.0.0-beta.13...v1.0.0-beta.14) (2022-06-01)

### 🐞 Bug Fixes

* add bounding box validity checking to geometry core functions and matchers ([ac6a9a1](ac6a9a1)), closes [/datatracker.ietf.org/doc/html/rfc7946#section-5](https://github.com/M-Scott-Lassiter//datatracker.ietf.org/doc/html/rfc7946/issues/section-5) [#9](#9) [#10](#10) [#11](#11) [#12](#12) [#13](#13) [#14](#14) [#16](#16) [#29](#29)
@M-Scott-Lassiter
Copy link
Owner Author

🎉 This issue has been resolved in version 1.0.0-beta.14 🎉

The release is available on:

Your semantic-release bot 📦🚀

Geometries automation moved this from In progress to Done Jun 1, 2022
This was referenced Jun 1, 2022
github-actions bot pushed a commit that referenced this issue Jun 2, 2022
## 1.0.0 (2022-06-02)

### 🧭 API Documentation Changes

* **toBeMultiLineStringGeometry:** add the min point count error to the JSDoc API ([01f6c4b](01f6c4b))
* change JSDoc param types to avoid using GeoJSON unknown types ([06ac03a](06ac03a))
* **all:** cleanup JSDoc formatting and standardize API examples ([300a96d](300a96d))
* update JSDoc descriptions of coordinate core functions ([f5658f3](f5658f3))
* update JSDoc organization ([7ab7eca](7ab7eca))
* **isValidCoordinate:** update returns description ([70bd43e](70bd43e))
* **isValidCoordinate:** update the error and parameter descriptions ([7e3e8ed](7e3e8ed))

### 🐞 Bug Fixes

* add bounding box validity checking to geometry core functions and matchers ([ac6a9a1](ac6a9a1)), closes [/datatracker.ietf.org/doc/html/rfc7946#section-5](https://github.com/M-Scott-Lassiter//datatracker.ietf.org/doc/html/rfc7946/issues/section-5) [#9](#9) [#10](#10) [#11](#11) [#12](#12) [#13](#13) [#14](#14) [#16](#16) [#29](#29)
* **toBeLineStringGeometry:** prohibit single coordinate in "coordinates" member ([ee5de52](ee5de52)), closes [/datatracker.ietf.org/doc/html/rfc7946#section-3](https://github.com/M-Scott-Lassiter//datatracker.ietf.org/doc/html/rfc7946/issues/section-3) [#11](#11)

### 🎯 Test Changes

* add 'Feature' and 'FeatureCollection' to test list of disallowed geometry type values ([f139a09](f139a09))
* **toBeLineStringGeometry:** add a stress test with many points ([22df5c7](22df5c7))
* **toBeGeometryCollection:** add an unrecognizable geometry to the invalid tests ([5c041c6](5c041c6))
* **toBeGeometryCollection:** add robust snapshot tests, verify coordinates treated as foreign member ([472d12d](472d12d)), closes [#32](#32) [#33](#33)
* **isValid2DBoundingBox:** add robust snapshot tests ([4363710](4363710)), closes [#32](#32)
* **isValid2DCoordinate:** add robust snapshot tests ([ae92f67](ae92f67)), closes [#32](#32)
* **isValid3DBoundingBox:** add robust snapshot tests ([a37ec48](a37ec48)), closes [#32](#32)
* **isValid3DCoordinate:** add robust snapshot tests ([56fbf92](56fbf92)), closes [#32](#32)
* **isValidBoundingBox:** add robust snapshot tests ([063b94e](063b94e)), closes [#32](#32)
* **isValidCoordinate:** add robust snapshot tests ([7b4a804](7b4a804)), closes [#32](#32)
* **toBeAnyGeometry:** add robust snapshot tests ([8a6e611](8a6e611)), closes [#32](#32)
* **toBeLineStringGeometry:** add robust snapshot tests ([cfaed46](cfaed46)), closes [#32](#32)
* **toBeMultiLineStringGeometry:** add robust snapshot tests ([57dc767](57dc767)), closes [#32](#32)
* **toBeMultiPointGeometry:** add robust snapshot tests ([36013e1](36013e1)), closes [#32](#32)
* **toBeMultiPolygonGeometry:** add robust snapshot tests ([df1c23a](df1c23a)), closes [#32](#32)
* **toBePointGeometry:** add robust snapshot tests ([fd5c516](fd5c516)), closes [#32](#32)
* **toBePolygonGeometry:** add robust snapshot tests ([1c9df69](1c9df69)), closes [#32](#32)
* **toBeMultiLineStringGeometry:** fix coordinate out of range test that ([d6fe2ac](d6fe2ac))
* **toBeMultiPointGeometry:** fix typo in test and core function that was omitting coverage ([bc10f4e](bc10f4e))
* **isValid2DBoundingBox:** fix typo in test descriptions ([df94c27](df94c27))
* setup the project testing framework ([6a95c37](6a95c37))

### 🎁 Feature Changes

* **isValid2DBoundingBox:** add new matcher function ([7fe56f3](7fe56f3)), closes [#6](#6)
* **isValid2DCoordinate:** add new matcher function ([527bbc4](527bbc4)), closes [#1](#1)
* **isValid3DCoordinate:** add new matcher function ([0329231](0329231)), closes [#2](#2)
* **isValidCoordinate:** add new matcher function ([d7e5b70](d7e5b70)), closes [#4](#4)
* **isValid3DBoundingBox:** add new matcher ([6ee8cc6](6ee8cc6)), closes [#7](#7)
* **isValidBoundingBox:** add new matcher ([9a8b7ed](9a8b7ed)), closes [#8](#8)
* **toBeAnyGeometry:** add new matcher ([ed7c3eb](ed7c3eb)), closes [#15](#15)
* **toBeFeature:** add new matcher ([551aa7f](551aa7f)), closes [#32](#32) [#24](#24)
* **toBeFeatureCollection:** add new matcher ([21fe044](21fe044)), closes [#25](#25)
* **toBeGeometryCollection:** add new matcher ([63cc919](63cc919)), closes [#16](#16)
* **toBeLineStringGeometry:** add new matcher ([54416a5](54416a5)), closes [#11](#11)
* **toBeMultiLineStringGeometry:** add new matcher ([3d3a15e](3d3a15e)), closes [#12](#12)
* **toBeMultiPointGeometry:** add new matcher ([9a12752](9a12752)), closes [#10](#10)
* **toBeMultiPolygonGeometry:** add new matcher ([41fef3a](41fef3a)), closes [#14](#14)
* **toBePointGeometry:** add new matcher ([9973afa](9973afa)), closes [#9](#9)
* **toBePolygonGeometry:** add new matcher ([3b9d18d](3b9d18d)), closes [#13](#13)
* split package exports into matcher and core functionality ([a7340d9](a7340d9)), closes [#5](#5)

### 🏗️ Build Changes

* add conventional-changelog-conventionalcommits as dev dependency ([7361d79](7361d79)), closes [#3](#3)
* **package:** add entry points for all, boundingboxes, and coordinates ([8c1d312](8c1d312))
* move release configuration into a separate shareable file ([fa6e50d](fa6e50d))
* **package:** rename matcher loader entry script ([5221d6d](5221d6d))
* setup initial project environment ([b468a41](b468a41))
* **package:** update the commitizen config for customized scopes ([ac05626](ac05626))
* **devDependencies:** upgrade Jest to v28.1, specify peerDependency at >v24.0.0 ([22d1614](22d1614)), closes [#30](#30)
* **package:** upgrade min required Node version to 16 ([58a9824](58a9824))
* **package:** upgrade minimum required node version from 10 to 14 to match LTS schedule ([1e8a8bb](1e8a8bb))
@M-Scott-Lassiter
Copy link
Owner Author

🎉 This issue has been resolved in version 1.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Geometries
  
Done
Development

No branches or pull requests

1 participant