Skip to content

Commit

Permalink
fix(geojson): simplify loop rewinding
Browse files Browse the repository at this point in the history
  • Loading branch information
missinglink committed Sep 24, 2024
1 parent 95881ab commit 213e8cb
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 133 deletions.
30 changes: 26 additions & 4 deletions geojson/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,24 @@ export const marshal = (loop: Loop, ordinal: number): geojson.Position[] => {
/**
* Constructs an s2 Loop given a geojson Polygon ring & ordinal.
* @category Constructors
*
* Handles differences between GeoJSON and S2:
* - GeoJSON rings are oriented CCW for the exterior and CW for holes, in S2 all loops are oriented CCW.
* - GeoJSON rings duplicate the start/end points, in S2 they do not.
*
* S2 Loops require the following properties be met:
* - Loops are not allowed to have any duplicate vertices (whether adjacent or not).
* - Non-adjacent edges are not allowed to intersect, and furthermore edges of length 180 degrees are not allowed (i.e., adjacent vertices cannot be antipodal).
* - Loops must have at least 3 vertices.
*/
export const unmarshal = (ring: geojson.Position[], ordinal: number): Loop => {
export const unmarshal = (ring: geojson.Position[]): Loop => {
if (ring.length < 3) return new Loop([])

ring = ring.slice() // make a copy to avoid mutating input
ring.length -= 1 // remove matching start/end points
if (ordinal > 0) ring.reverse() // ensure all rings are CCW
if (clockwise(ring)) ring.reverse() // all rings must be CCW
if (position.equal(ring.at(0)!, ring.at(-1)!)) ring.length -= 1 // remove matching start/end points

// Loops are not allowed to have any duplicate vertices (whether adjacent or not)
// Loops are not allowed to have duplicate vertices (whether adjacent or not)
if (containsDuplicateVertices(ring)) {
// adjacent duplicates are fixable
ring = removeAdjacentDuplicateVertices(ring)
Expand All @@ -47,3 +58,14 @@ export const removeAdjacentDuplicateVertices = (ring: geojson.Position[], epsilo
export const containsDuplicateVertices = (ring: geojson.Position[]): boolean => {
return new Set(ring.map((c) => `${c[0]}|${c[1]}`)).size !== ring.length
}

/**
* Returns true IFF ring is oriented Clockwise.
*/
export const clockwise = (ring: geojson.Position[]): boolean => {
let sum = 0
for (let i = 1; i < ring.length; i++) {
sum += (ring[i][0] - ring[i - 1][0]) * (ring[i][1] + ring[i - 1][1])
}
return sum > 0
}
11 changes: 3 additions & 8 deletions geojson/polygon.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type * as geojson from 'geojson'
import * as loop from './loop'
import { Polygon } from '../s2/Polygon'
import { rewindPolygon } from './turf'

/**
* Returns a geojson Polygon geometry given an s2 Polygon.
Expand All @@ -18,8 +17,7 @@ export const marshal = (polygon: Polygon): geojson.Polygon => {
* Constructs an s2 Polygon given a geojson Polygon geometry.
* @category Constructors
*/
export const unmarshal = (geometry: geojson.Polygon, rewind = true): Polygon => {
if (rewind) rewindPolygon(geometry.coordinates, false)
export const unmarshal = (geometry: geojson.Polygon): Polygon => {
return new Polygon(geometry.coordinates.map(loop.unmarshal))
}

Expand All @@ -38,9 +36,6 @@ export const marshalMulti = (polygons: Polygon[]): geojson.MultiPolygon => {
* Constructs s2 Polygons given a geojson MultiPolygon geometry.
* @category Constructors
*/
export const unmarshalMulti = (geometry: geojson.MultiPolygon, rewind = true): Polygon[] => {
return geometry.coordinates.map((coords) => {
if (rewind) rewindPolygon(coords, false)
return new Polygon(coords.map(loop.unmarshal))
})
export const unmarshalMulti = (geometry: geojson.MultiPolygon): Polygon[] => {
return geometry.coordinates.map((coords) => new Polygon(coords.map(loop.unmarshal)))
}
121 changes: 0 additions & 121 deletions geojson/turf.ts

This file was deleted.

0 comments on commit 213e8cb

Please sign in to comment.