Skip to content

Commit

Permalink
fix(geojson): detect invalid polygon loops
Browse files Browse the repository at this point in the history
  • Loading branch information
missinglink committed Sep 20, 2024
1 parent 4d7a221 commit cb6c8ee
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 1 deletion.
47 changes: 47 additions & 0 deletions geojson/RegionCoverer_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,51 @@ describe('RegionCoverer', () => {
['48795eb9', '48795ec4', '48795ed04', '48795ed0c', '48795ed74', '48795edc', '48795ee7c', '48795ee84']
)
})

test('polygon - twisted + contains duplicate vertices', (t) => {
const mpolygon: geojson.MultiPolygon = {
type: 'MultiPolygon',
coordinates: [
[
[
[-122.420357, 37.651333],
[-122.42047100000001, 37.652073],
[-122.421204, 37.651173],
[-122.42038700000001, 37.651276],
[-122.405418, 37.634312],
[-122.400352, 37.634029],
[-122.39450100000001, 37.632862],
[-122.388313, 37.633675],
[-122.37602200000001, 37.631088],
[-122.362549, 37.638908],
[-122.360283, 37.634068],
[-122.35611, 37.614025],
[-122.353706, 37.612396],
[-122.351601, 37.61134],
[-122.349411, 37.610287],
[-122.345123, 37.607704],
[-122.34137699999999, 37.590256],
[-122.359131, 37.585941],
[-122.36784400000001, 37.600216],
[-122.37653400000001, 37.605061],
[-122.380539, 37.607029],
[-122.383797, 37.607666],
[-122.395447, 37.60276],
[-122.401848, 37.605137],
[-122.404831, 37.611164],
[-122.405632, 37.613293],
[-122.40589900000001, 37.614941],
[-122.40582999999999, 37.615002],
[-122.40129899999999, 37.625465],
[-122.405418, 37.634312],
[-122.420357, 37.651333]
]
]
]
}

const cov = new RegionCoverer()
const union = cov.covering(mpolygon)
deepEqual([...union.map(cellid.toToken)], []) // cannot be fixed, return []
})
})
25 changes: 24 additions & 1 deletion geojson/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,30 @@ export const marshal = (loop: Loop, ordinal: number): geojson.Position[] => {
export const unmarshal = (ring: geojson.Position[], ordinal: number): Loop => {
ring = ring.slice() // make a copy to avoid mutating input
ring.length -= 1 // remove matching start/end points
ring = ring.filter((p, i) => !i || !position.equal(ring.at(i - 1)!, p, 0)) // remove equal+adjacent vertices
if (ordinal > 0) ring.reverse() // ensure all rings are CCW

// Loops are not allowed to have any duplicate vertices (whether adjacent or not)
if (containsDuplicateVertices(ring)) {
// adjacent duplicates are fixable
ring = removeAdjacentDuplicateVertices(ring)

// non-adjacent duplicates are not fixable
if (containsDuplicateVertices(ring)) return new Loop([])
}

return new Loop(ring.map(position.unmarshal))
}

/**
* Removes *adjacent* duplicate (and near-duplicate) vertices from ring.
*/
export const removeAdjacentDuplicateVertices = (ring: geojson.Position[], epsilon = 1e-8): geojson.Position[] => {
return ring.filter((p, i) => !i || !position.equal(ring.at(i - 1)!, p, epsilon))
}

/**
* Returns true IFF ring contains duplicate vertices at any position.
*/
export const containsDuplicateVertices = (ring: geojson.Position[]): boolean => {
return ring.some((A, i) => ring.slice(i + 1).some((B) => position.equal(A, B)))
}

0 comments on commit cb6c8ee

Please sign in to comment.