diff --git a/packages/turf-polygon-tangents/index.ts b/packages/turf-polygon-tangents/index.ts index 7e27783ca6..7d62105131 100644 --- a/packages/turf-polygon-tangents/index.ts +++ b/packages/turf-polygon-tangents/index.ts @@ -75,6 +75,26 @@ function polygonTangents( rtan, ltan ); + // When the nearest-vertex initialisation sets ltan === rtan (both below + // the viewpoint), processPolygon may fail to find a distinct left tangent + // because every above-viewpoint candidate is considered "above" the + // current ltan and therefore not chosen. Detect this degenerate result + // and retry with ltan reset to vertex[0] so the traversal can pick the + // correct left tangent. See https://github.com/Turfjs/turf/issues/2898 + if ( + nearestPtIndex !== 0 && + rtan[0] === ltan[0] && + rtan[1] === ltan[1] + ) { + ltan = polyCoords[0][0]; + [rtan, ltan] = processPolygon( + polyCoords[0], + pointCoords, + eprev, + rtan, + ltan + ); + } break; case "MultiPolygon": var closestFeature = 0; diff --git a/packages/turf-polygon-tangents/test.ts b/packages/turf-polygon-tangents/test.ts index cae2f35ab5..fe9cadb340 100644 --- a/packages/turf-polygon-tangents/test.ts +++ b/packages/turf-polygon-tangents/test.ts @@ -109,3 +109,24 @@ test("turf-polygon-tangents - Issue #1050", (t) => { } t.end(); }); + +test("turf-polygon-tangents - Issue #2898", (t) => { + // When the viewpoint is inside the polygon bbox and the nearest vertex is + // below the viewpoint, polygonTangents previously returned the same point + // for both tangents instead of two distinct tangent vertices. + const pt = point([-79.41285676230808, 43.627309605975235]); + const poly = polygon([ + [ + [-79.428776, 43.708224], + [-79.325734, 43.675502], + [-79.414155, 43.595383], + [-79.353931, 43.670969], + [-79.428776, 43.708224], + ], + ]); + const tangents = polygonTangents(pt, poly); + t.equal(tangents.features.length, 2, "returns two features"); + const [t1, t2] = tangents.features.map((f) => f.geometry.coordinates); + t.notDeepEqual(t1, t2, "tangent points must be distinct vertices"); + t.end(); +}); diff --git a/packages/turf-polygon-tangents/test/in/issue#2898.geojson b/packages/turf-polygon-tangents/test/in/issue#2898.geojson new file mode 100644 index 0000000000..e13e8691d2 --- /dev/null +++ b/packages/turf-polygon-tangents/test/in/issue#2898.geojson @@ -0,0 +1,29 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-79.428776, 43.708224], + [-79.325734, 43.675502], + [-79.414155, 43.595383], + [-79.353931, 43.670969], + [-79.428776, 43.708224] + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [-79.41285676230808, 43.627309605975235] + } + } + ] +} diff --git a/packages/turf-polygon-tangents/test/out/issue#2898.geojson b/packages/turf-polygon-tangents/test/out/issue#2898.geojson new file mode 100644 index 0000000000..d09e2e6b5e --- /dev/null +++ b/packages/turf-polygon-tangents/test/out/issue#2898.geojson @@ -0,0 +1,69 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -79.414155, + 43.595383 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -79.428776, + 43.708224 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.428776, + 43.708224 + ], + [ + -79.325734, + 43.675502 + ], + [ + -79.414155, + 43.595383 + ], + [ + -79.353931, + 43.670969 + ], + [ + -79.428776, + 43.708224 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -79.41285676230808, + 43.627309605975235 + ] + } + } + ] +} \ No newline at end of file