From c0a4dec6c12228880b1c562c1970049b7a420e8d Mon Sep 17 00:00:00 2001 From: Mike Fogel Date: Wed, 14 Nov 2018 15:33:38 -0300 Subject: [PATCH] Support polys with overlapping segments Closes #48 --- README.md | 4 +++ src/segment.js | 17 ++++++++-- src/sweep-event.js | 8 +++-- .../infinitely-thin-polygon/args.geojson | 33 +++++++++++++++++++ .../difference.geojson | 14 ++++++++ .../intersection.geojson | 11 +++++++ .../infinitely-thin-polygon/union.geojson | 8 +++++ test/segment.test.js | 6 ++-- test/sweep-event.test.js | 16 +++------ 9 files changed, 98 insertions(+), 19 deletions(-) create mode 100644 test/end-to-end/infinitely-thin-polygon/args.geojson create mode 100644 test/end-to-end/infinitely-thin-polygon/difference.geojson create mode 100644 test/end-to-end/infinitely-thin-polygon/intersection.geojson create mode 100644 test/end-to-end/infinitely-thin-polygon/union.geojson diff --git a/README.md b/README.md index 3fd31f0..e62fe1d 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,10 @@ The Martinez-Rueda-Feito polygon clipping algorithm is used to compute the resul ## Changelog +### vNext + + * Support polygons with infinitely thin sections ([#48](https://github.com/mfogel/polygon-clipping/issues/48)) + ### v0.9.1 (2018-11-12) * Bug fixes ([#36](https://github.com/mfogel/polygon-clipping/issues/36) again, [#44](https://github.com/mfogel/polygon-clipping/issues/44)) diff --git a/src/segment.js b/src/segment.js index 8b02f3d..70ef803 100644 --- a/src/segment.js +++ b/src/segment.js @@ -40,6 +40,12 @@ export default class Segment { if (a.ringIn.id !== b.ringIn.id) { return a.ringIn.id < b.ringIn.id ? -1 : 1 } + + // overlapping segments from the same ring + // https://github.com/mfogel/polygon-clipping/issues/48 + if (a.tiebreaker < b.tiebreaker) return -1 + if (a.tiebreaker > b.tiebreaker) return 1 + } else { // not colinear @@ -65,9 +71,8 @@ export default class Segment { } throw new Error( - `Segment comparison (from [${a.leftSE.point.x}, ${a.leftSE.point.y}])` + - ` -> to [${a.rightSE.point.x}, ${a.rightSE.point.y}]) failed... ` + - ` segments equal but not identical?` + `Segment comparison from [${a.leftSE.point.x}, ${a.leftSE.point.y}]` + + ` to [${a.rightSE.point.x}, ${a.rightSE.point.y}] failed` ) } @@ -99,6 +104,12 @@ export default class Segment { return new Segment(leftSE, rightSE, ring) } + // used for sorting equal sweep events, segments consistently + get tiebreaker () { + if (this._tiebreaker === undefined) this._tiebreaker = Math.random() + return this._tiebreaker + } + get bbox () { const y1 = this.leftSE.point.y const y2 = this.rightSE.point.y diff --git a/src/sweep-event.js b/src/sweep-event.js index d5c9f1c..2d5887a 100644 --- a/src/sweep-event.js +++ b/src/sweep-event.js @@ -43,9 +43,13 @@ export default class SweepEvent { // they appear to be the same point... are they? if (a === b) return 0 + // they're from overlapping segments of the same ring + // https://github.com/mfogel/polygon-clipping/issues/48 + if (a.segment.tiebreaker < b.segment.tiebreaker) return -1 + if (a.segment.tiebreaker > b.segment.tiebreaker) return 1 + throw new Error( - `SweepEvent comparison failed at [${a.point.x}, ${a.point.y}]... ` + - `equal but not identical?` + `SweepEvent comparison failed at [${a.point.x}, ${a.point.y}]` ) } diff --git a/test/end-to-end/infinitely-thin-polygon/args.geojson b/test/end-to-end/infinitely-thin-polygon/args.geojson new file mode 100644 index 0000000..edecb03 --- /dev/null +++ b/test/end-to-end/infinitely-thin-polygon/args.geojson @@ -0,0 +1,33 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [[[-3, -3], [3, -3], [3, 3], [-3, 3], [-3, -3]]] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-2, -1], + [-1, 0], + [1, 0], + [2, -1], + [2, 1], + [1, 0], + [-1, 0], + [-2, 1], + [-2, -1] + ] + ] + } + } + ] +} diff --git a/test/end-to-end/infinitely-thin-polygon/difference.geojson b/test/end-to-end/infinitely-thin-polygon/difference.geojson new file mode 100644 index 0000000..1faf308 --- /dev/null +++ b/test/end-to-end/infinitely-thin-polygon/difference.geojson @@ -0,0 +1,14 @@ +{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [[-3, -3], [3, -3], [3, 3], [-3, 3], [-3, -3]], + [[-2, -1], [-2, 1], [-1, 0], [-2, -1]], + [[1, 0], [2, 1], [2, -1], [1, 0]] + ] + ] + } +} diff --git a/test/end-to-end/infinitely-thin-polygon/intersection.geojson b/test/end-to-end/infinitely-thin-polygon/intersection.geojson new file mode 100644 index 0000000..6a4ef9c --- /dev/null +++ b/test/end-to-end/infinitely-thin-polygon/intersection.geojson @@ -0,0 +1,11 @@ +{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [[[-2, -1], [-1, 0], [-2, 1], [-2, -1]]], + [[[1, 0], [2, -1], [2, 1], [1, 0]]] + ] + } +} diff --git a/test/end-to-end/infinitely-thin-polygon/union.geojson b/test/end-to-end/infinitely-thin-polygon/union.geojson new file mode 100644 index 0000000..fcf87ec --- /dev/null +++ b/test/end-to-end/infinitely-thin-polygon/union.geojson @@ -0,0 +1,8 @@ +{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[-3, -3], [3, -3], [3, 3], [-3, 3], [-3, -3]]]] + } +} diff --git a/test/segment.test.js b/test/segment.test.js index 5f308c0..0e48661 100644 --- a/test/segment.test.js +++ b/test/segment.test.js @@ -817,9 +817,11 @@ describe('compare segments', () => { expect(Segment.compare(seg2, seg1)).toBe(1) }) - test('exactly equal segments (but not identical) should throw error', () => { + test('exactly equal segments (but not identical) are consistent', () => { const seg1 = Segment.fromRing({ x: 0, y: 0 }, { x: 4, y: 4 }, { id: 1 }) const seg2 = Segment.fromRing({ x: 0, y: 0 }, { x: 4, y: 4 }, { id: 1 }) - expect(() => Segment.compare(seg1, seg2)).toThrow() + const result = Segment.compare(seg1, seg2) + expect(Segment.compare(seg1, seg2)).toBe(result) + expect(Segment.compare(seg2, seg1)).toBe(result * -1) }) }) diff --git a/test/sweep-event.test.js b/test/sweep-event.test.js index 71741d6..09ec3a4 100644 --- a/test/sweep-event.test.js +++ b/test/sweep-event.test.js @@ -61,23 +61,15 @@ describe('sweep event compare', () => { expect(SweepEvent.compare(s1, s1)).toBe(0) }) - test('totally equal but not identical', () => { + test('totally equal but not identical events are consistent', () => { const s1 = new SweepEvent({ x: 0, y: 0 }) const s2 = new SweepEvent({ x: 0, y: 0 }) const s3 = new SweepEvent({ x: 3, y: 3 }) new Segment(s1, s3, { id: 1 }) new Segment(s2, s3, { id: 1 }) - expect(() => SweepEvent.compare(s1, s2)).toThrow() - }) - - test('length does not matter', () => { - const s1 = new SweepEvent({ x: 0, y: 0 }) - const s2 = new SweepEvent({ x: 0, y: 0 }) - const s3 = new SweepEvent({ x: 3, y: 3 }) - const s4 = new SweepEvent({ x: 4, y: 4 }) - new Segment(s1, s3, { id: 1 }) - new Segment(s2, s4, { id: 1 }) - expect(() => SweepEvent.compare(s1, s2)).toThrow() + const result = SweepEvent.compare(s1, s2) + expect(SweepEvent.compare(s1, s2)).toBe(result) + expect(SweepEvent.compare(s2, s1)).toBe(result * -1) }) test('events are linked as side effect', () => {