@turf/buffer gives invalid inner buffer around closed LineString
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 10.5k
- Forks
- 1k
- Avg merge
- 1h 11m
- Merged PRs (30d)
- 4
Description
Hi,
when creating a buffer around a LineString which has the same start vertex and the same end vertex the calculated inner buffer is wrong under special circumstances.
Here is a minimal code example which reproduces the wrong buffer. The first test case is a closed LineString which has a hole when creating a buffer of 500 meters. The second test case is a slightly different closed LineString which has no hole when creating a buffer of 500 meters around it. The third test case is a different example with a buffer of 2000 meters and an even bigger hole in the which shouldnt belong there.
import { buffer } from "@turf/turf";
import { LineString } from "geojson";
import { expect, it } from "vitest";
const bufferOptions: Parameters<typeof buffer>[2] = {
units: "meters",
steps: 0,
};
it.each<[string, LineString, number, number]>([
[
"Corridor with buffer should have hole",
{
type: "LineString",
coordinates: [
[11.9864449, 52.9652597],
[12.0249011, 52.9771682],
[12.0622777, 52.9652798],
[11.9864449, 52.9652597],
],
},
500,
2,
],
[
"Corridor with buffer shouldn't have hole",
{
type: "LineString",
coordinates: [
[11.9864449, 52.9652597],
[12.0243343, 52.9740445],
[12.0622777, 52.9652798],
[11.9864449, 52.9652597],
],
},
500,
1,
],
[
"Corridor with buffer shouldn't have hole",
{
type: "LineString",
coordinates: [
[12.4102822, 53.6462527],
[12.3735781, 53.6041575],
[12.4513151, 53.615602],
[12.4102822, 53.6462527],
],
},
2000,
1,
],
])("$1", (_name, lineString, radius, numRings) => {
const corridor = buffer(lineString, radius, bufferOptions)!;
expect(corridor.geometry.coordinates.length).toEqual(numRings);
});
The first test case where a hole is expected.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[11.9864449, 52.9652597],
[12.0249011, 52.9771682],
[12.0622777, 52.9652798],
[11.9864449, 52.9652597]
]
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[11.986452128660401, 52.960763100408464],
[12.062277041615708, 52.9607831983179],
[12.063684519759718, 52.960863761914545],
[12.065041550468617, 52.96110271332033],
[12.066299470082624, 52.96149148385769],
[12.067413166027833, 52.96201613211102],
[12.068342693716165, 52.96265784346482],
[12.069054708826204, 52.96339360433853],
[12.069523663664947, 52.964197027024746],
[12.069732724654596, 52.965039295637716],
[12.069674377879451, 52.96589019932362],
[12.069350700726773, 52.966719215730365],
[12.068773289577013, 52.96749660590088],
[12.067962845823466, 52.96819448130805],
[12.066948434788726, 52.96878780471482],
[12.065766443910471, 52.969255288887574],
[12.028387364746067, 52.981144782741886],
[12.02706246923208, 52.98147236900115],
[12.025658060755003, 52.98164164428673],
[12.02422580470309, 52.9816463812231],
[12.022818391870915, 52.98148640554473],
[12.021487598272255, 52.98116760251265],
[11.983028781062549, 52.96925800051954],
[11.98183337337055, 52.96879600437097],
[11.980804897063242, 52.96820602234372],
[11.979980573410897, 52.96750941060336],
[11.979390229493612, 52.96673138398435],
[11.979055219500575, 52.96590010282219],
[11.978987653405312, 52.965045653317176],
[11.979189960718271, 52.96419895840421],
[11.979654804815691, 52.96339065858894],
[11.980365350622026, 52.96265000326192],
[11.98129587564726, 52.96200379258628],
[11.982412702011077, 52.96147540819164],
[11.983675415562319, 52.96108396766854],
[11.985038327928422, 52.960843633365506],
[11.986452128660401, 52.960763100408464]
],
[
[12.017333025212631, 52.96977031465849],
[12.024854857987961, 52.972098339869596],
[12.032165528799508, 52.96977419741604],
[12.017333025212631, 52.96977031465849]
]
]
}
}
]
}
The second test case where no hole is expected. Also noteworthy is that the calculated hole is kind of mirrored/inverted.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[11.9864449, 52.9652597],
[12.0243343, 52.9740445],
[12.0622777, 52.9652798],
[11.9864449, 52.9652597]
]
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[11.986452128633372, 52.96076310040846],
[12.062277041642751, 52.96078319831792],
[12.063747133387176, 52.96087116589199],
[12.065159692119654, 52.961131780607424],
[12.066459409642427, 52.961554838473894],
[12.067595392214091, 52.96212377488877],
[12.068523152296521, 52.96281631269754],
[12.06920635040717, 52.9636053338648],
[12.069618218935268, 52.964459940742934],
[12.069742612068522, 52.96534666549963],
[12.069574640466282, 52.966230780442125],
[12.069120865462224, 52.96707765798852],
[12.068399044770057, 52.96785412704424],
[12.067437439235782, 52.96852977263269],
[12.066273707440773, 52.9690781278045],
[12.064953431224962, 52.96947771103499],
[12.027006887787016, 52.978243262558884],
[12.025691007582523, 52.978466269277284],
[12.024329654362797, 52.978541100948505],
[12.022968456852539, 52.97846524941931],
[12.02165303855271, 52.9782412570257],
[11.983760505127004, 52.96945560395751],
[11.98244121918252, 52.9690550948594],
[11.98127873731212, 52.96850596750384],
[11.980318580480697, 52.967829728048024],
[11.979598340679749, 52.96705285978615],
[11.979146209448798, 52.96620578538327],
[11.978979875485422, 52.965321674977176],
[11.979105834283425, 52.9644351469261],
[11.97951913648318, 52.963580912149034],
[11.980203584371624, 52.9627924151678],
[11.981132368418002, 52.9621005250386],
[11.98226911856279, 52.961532327360345],
[11.983569328854063, 52.961110064552784],
[11.984982099561302, 52.960850265772564],
[11.986452128633372, 52.96076310040846]
],
[
[12.02669255276277, 52.96977297957135],
[12.024339275254995, 52.96922781769787],
[12.021982645144059, 52.96977173037168],
[12.02669255276277, 52.96977297957135]
]
]
}
}
]
}
The third test case where no hole is expected. Here the wrong inner hole is better visible.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[12.4102822, 53.6462527],
[12.3735781, 53.6041575],
[12.4513151, 53.615602],
[12.4102822, 53.6462527]
]
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[12.434048190429264, 53.65743746814345],
[12.429841883642712, 53.66000497984985],
[12.424860610473045, 53.66202802983935],
[12.419301649861998, 53.66342645795963],
[12.413385247348295, 53.66414484704941],
[12.407345864483649, 53.664154726539344],
[12.401422854912267, 53.66345570488502],
[12.39585094299714, 53.6620754851424],
[12.390850889294208, 53.6600687630567],
[12.386620719209533, 53.657515052124324],
[12.383327867230836, 53.654515523282534],
[12.346643469754003, 53.612412084962976],
[12.344493313764195, 53.60922861559079],
[12.343400950658962, 53.605860882939936],
[12.34340584950627, 53.602431354553005],
[12.344507568178525, 53.59906471542497],
[12.346665807473236, 53.595883334947686],
[12.349801911479396, 53.593002822243626],
[12.353801754574436, 53.5905278308667],
[12.358519906166206, 53.58854826426606],
[12.363784919542637, 53.58713601848681],
[12.369405552340158, 53.586342378911134],
[12.37517769431382, 53.58619616415882],
[12.380891754152904, 53.58670268341412],
[12.45859860683452, 53.59814245377081],
[12.464295404451546, 53.59934778948034],
[12.469475927693365, 53.601200132664594],
[12.473934034403996, 53.60362580403688],
[12.477492233375447, 53.60652830501132],
[12.480008747692269, 53.60979214611756],
[12.481383174732379, 53.61328743223641],
[12.481560515172976, 53.616875024194066],
[12.480533405695052, 53.62041207287481],
[12.478342459767518, 53.62375770648294],
[12.475074695439954, 53.6267786446326],
[12.434048190429264, 53.65743746814345]
],
[
[12.415680155344077, 53.61330297239273],
[12.398237388931744, 53.62632681525116],
[12.431292590222798, 53.63119037277658],
[12.415680155344077, 53.61330297239273]
]
]
}
}
]
}
Latest Turf version is used which was 7.2.0 at the time of filing this bug report.
It may be related to #2920.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the supplied TypeScript reproduction against @turf/buffer in Turf 7.2.0, then trace the buffer implementation and compare its output rings with the three expected cases. Done means the first case retains its expected hole while the other two do not produce inverted or spurious inner holes; issue #2920 may provide related context.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100