@turf/unkink-polygon
Version:
turf unkink-polygon module
579 lines (576 loc) • 19.3 kB
JavaScript
// index.ts
import { flattenEach, featureEach } from "@turf/meta";
import { polygon as polygon2, featureCollection as featureCollection2 } from "@turf/helpers";
// lib/geojson-polygon-self-intersections.js
import rbush from "rbush";
function geojsonPolygonSelfIntersections(feature, filterFn, useSpatialIndex) {
if (feature.geometry.type !== "Polygon")
throw new Error("The input feature must be a Polygon");
if (useSpatialIndex === void 0) useSpatialIndex = 1;
var coord = feature.geometry.coordinates;
var output = [];
var seen = {};
if (useSpatialIndex) {
var allEdgesAsRbushTreeItems = [];
for (var ring0 = 0; ring0 < coord.length; ring0++) {
for (var edge0 = 0; edge0 < coord[ring0].length - 1; edge0++) {
allEdgesAsRbushTreeItems.push(rbushTreeItem(ring0, edge0));
}
}
var tree = new rbush();
tree.load(allEdgesAsRbushTreeItems);
}
for (var ringA = 0; ringA < coord.length; ringA++) {
for (var edgeA = 0; edgeA < coord[ringA].length - 1; edgeA++) {
if (useSpatialIndex) {
var bboxOverlaps = tree.search(rbushTreeItem(ringA, edgeA));
bboxOverlaps.forEach(function(bboxIsect) {
var ring12 = bboxIsect.ring;
var edge12 = bboxIsect.edge;
ifIsectAddToOutput(ringA, edgeA, ring12, edge12);
});
} else {
for (var ring1 = 0; ring1 < coord.length; ring1++) {
for (var edge1 = 0; edge1 < coord[ring1].length - 1; edge1++) {
ifIsectAddToOutput(ringA, edgeA, ring1, edge1);
}
}
}
}
}
if (!filterFn)
output = {
type: "Feature",
geometry: { type: "MultiPoint", coordinates: output }
};
return output;
function ifIsectAddToOutput(ring02, edge02, ring12, edge12) {
var start0 = coord[ring02][edge02];
var end0 = coord[ring02][edge02 + 1];
var start1 = coord[ring12][edge12];
var end1 = coord[ring12][edge12 + 1];
var isect = intersect(start0, end0, start1, end1);
if (isect === null) return;
var frac0;
var frac1;
if (end0[0] !== start0[0]) {
frac0 = (isect[0] - start0[0]) / (end0[0] - start0[0]);
} else {
frac0 = (isect[1] - start0[1]) / (end0[1] - start0[1]);
}
if (end1[0] !== start1[0]) {
frac1 = (isect[0] - start1[0]) / (end1[0] - start1[0]);
} else {
frac1 = (isect[1] - start1[1]) / (end1[1] - start1[1]);
}
if (frac0 >= 1 || frac0 <= 0 || frac1 >= 1 || frac1 <= 0) return;
var key = isect;
var unique = !seen[key];
if (unique) {
seen[key] = true;
}
if (filterFn) {
output.push(
filterFn(
isect,
ring02,
edge02,
start0,
end0,
frac0,
ring12,
edge12,
start1,
end1,
frac1,
unique
)
);
} else {
output.push(isect);
}
}
function rbushTreeItem(ring, edge) {
var start = coord[ring][edge];
var end = coord[ring][edge + 1];
var minX;
var maxX;
var minY;
var maxY;
if (start[0] < end[0]) {
minX = start[0];
maxX = end[0];
} else {
minX = end[0];
maxX = start[0];
}
if (start[1] < end[1]) {
minY = start[1];
maxY = end[1];
} else {
minY = end[1];
maxY = start[1];
}
return {
minX,
minY,
maxX,
maxY,
ring,
edge
};
}
}
function intersect(start0, end0, start1, end1) {
if (equalArrays(start0, start1) || equalArrays(start0, end1) || equalArrays(end0, start1) || equalArrays(end1, start1))
return null;
var x0 = start0[0], y0 = start0[1], x1 = end0[0], y1 = end0[1], x2 = start1[0], y2 = start1[1], x3 = end1[0], y3 = end1[1];
var denom = (x0 - x1) * (y2 - y3) - (y0 - y1) * (x2 - x3);
if (denom === 0) return null;
var x4 = ((x0 * y1 - y0 * x1) * (x2 - x3) - (x0 - x1) * (x2 * y3 - y2 * x3)) / denom;
var y4 = ((x0 * y1 - y0 * x1) * (y2 - y3) - (y0 - y1) * (x2 * y3 - y2 * x3)) / denom;
return [x4, y4];
}
function equalArrays(array1, array2) {
if (!array1 || !array2) return false;
if (array1.length !== array2.length) return false;
for (var i = 0, l = array1.length; i < l; i++) {
if (array1[i] instanceof Array && array2[i] instanceof Array) {
if (!equalArrays(array1[i], array2[i])) return false;
} else if (array1[i] !== array2[i]) {
return false;
}
}
return true;
}
// lib/simplepolygon.js
import { area } from "@turf/area";
import { featureCollection, polygon } from "@turf/helpers";
import { booleanPointInPolygon } from "@turf/boolean-point-in-polygon";
import rbush2 from "rbush";
function simplepolygon(feature) {
if (feature.type != "Feature")
throw new Error("The input must a geojson object of type Feature");
if (feature.geometry === void 0 || feature.geometry == null)
throw new Error(
"The input must a geojson object with a non-empty geometry"
);
if (feature.geometry.type != "Polygon")
throw new Error("The input must be a geojson Polygon");
var numRings = feature.geometry.coordinates.length;
var vertices = [];
for (var i = 0; i < numRings; i++) {
var ring = feature.geometry.coordinates[i];
if (!equalArrays2(ring[0], ring[ring.length - 1])) {
ring.push(ring[0]);
}
for (var j = 0; j < ring.length - 1; j++) {
vertices.push(ring[j]);
}
}
if (!isUnique(vertices))
throw new Error(
"The input polygon may not have duplicate vertices (except for the first and last vertex of each ring)"
);
var numvertices = vertices.length;
var selfIsectsData = geojsonPolygonSelfIntersections(
feature,
function filterFn(isect, ring0, edge0, start0, end0, frac0, ring1, edge1, start1, end1, frac1, unique) {
return [
isect,
ring0,
edge0,
start0,
end0,
frac0,
ring1,
edge1,
start1,
end1,
frac1,
unique
];
}
);
var numSelfIsect = selfIsectsData.length;
if (numSelfIsect == 0) {
var outputFeatureArray = [];
for (var i = 0; i < numRings; i++) {
outputFeatureArray.push(
polygon([feature.geometry.coordinates[i]], {
parent: -1,
winding: windingOfRing(feature.geometry.coordinates[i])
})
);
}
var output = featureCollection(outputFeatureArray);
determineParents();
setNetWinding();
return output;
}
var pseudoVtxListByRingAndEdge = [];
var isectList = [];
for (var i = 0; i < numRings; i++) {
pseudoVtxListByRingAndEdge.push([]);
for (var j = 0; j < feature.geometry.coordinates[i].length - 1; j++) {
pseudoVtxListByRingAndEdge[i].push([
new PseudoVtx(
feature.geometry.coordinates[i][modulo(j + 1, feature.geometry.coordinates[i].length - 1)],
1,
[i, j],
[i, modulo(j + 1, feature.geometry.coordinates[i].length - 1)],
void 0
)
]);
isectList.push(
new Isect(
feature.geometry.coordinates[i][j],
[i, modulo(j - 1, feature.geometry.coordinates[i].length - 1)],
[i, j],
void 0,
void 0,
false,
true
)
);
}
}
for (var i = 0; i < numSelfIsect; i++) {
pseudoVtxListByRingAndEdge[selfIsectsData[i][1]][selfIsectsData[i][2]].push(
new PseudoVtx(
selfIsectsData[i][0],
selfIsectsData[i][5],
[selfIsectsData[i][1], selfIsectsData[i][2]],
[selfIsectsData[i][6], selfIsectsData[i][7]],
void 0
)
);
if (selfIsectsData[i][11])
isectList.push(
new Isect(
selfIsectsData[i][0],
[selfIsectsData[i][1], selfIsectsData[i][2]],
[selfIsectsData[i][6], selfIsectsData[i][7]],
void 0,
void 0,
true,
true
)
);
}
var numIsect = isectList.length;
for (var i = 0; i < pseudoVtxListByRingAndEdge.length; i++) {
for (var j = 0; j < pseudoVtxListByRingAndEdge[i].length; j++) {
pseudoVtxListByRingAndEdge[i][j].sort(function(a, b) {
return a.param < b.param ? -1 : 1;
});
}
}
var allIsectsAsIsectRbushTreeItem = [];
for (var i = 0; i < numIsect; i++) {
allIsectsAsIsectRbushTreeItem.push({
minX: isectList[i].coord[0],
minY: isectList[i].coord[1],
maxX: isectList[i].coord[0],
maxY: isectList[i].coord[1],
index: i
});
}
var isectRbushTree = new rbush2();
isectRbushTree.load(allIsectsAsIsectRbushTreeItem);
for (var i = 0; i < pseudoVtxListByRingAndEdge.length; i++) {
for (var j = 0; j < pseudoVtxListByRingAndEdge[i].length; j++) {
for (var k = 0; k < pseudoVtxListByRingAndEdge[i][j].length; k++) {
var coordToFind;
if (k == pseudoVtxListByRingAndEdge[i][j].length - 1) {
coordToFind = pseudoVtxListByRingAndEdge[i][modulo(j + 1, feature.geometry.coordinates[i].length - 1)][0].coord;
} else {
coordToFind = pseudoVtxListByRingAndEdge[i][j][k + 1].coord;
}
var IsectRbushTreeItemFound = isectRbushTree.search({
minX: coordToFind[0],
minY: coordToFind[1],
maxX: coordToFind[0],
maxY: coordToFind[1]
})[0];
pseudoVtxListByRingAndEdge[i][j][k].nxtIsectAlongEdgeIn = IsectRbushTreeItemFound.index;
}
}
}
for (var i = 0; i < pseudoVtxListByRingAndEdge.length; i++) {
for (var j = 0; j < pseudoVtxListByRingAndEdge[i].length; j++) {
for (var k = 0; k < pseudoVtxListByRingAndEdge[i][j].length; k++) {
var coordToFind = pseudoVtxListByRingAndEdge[i][j][k].coord;
var IsectRbushTreeItemFound = isectRbushTree.search({
minX: coordToFind[0],
minY: coordToFind[1],
maxX: coordToFind[0],
maxY: coordToFind[1]
})[0];
var l = IsectRbushTreeItemFound.index;
if (l < numvertices) {
isectList[l].nxtIsectAlongRingAndEdge2 = pseudoVtxListByRingAndEdge[i][j][k].nxtIsectAlongEdgeIn;
} else {
if (equalArrays2(
isectList[l].ringAndEdge1,
pseudoVtxListByRingAndEdge[i][j][k].ringAndEdgeIn
)) {
isectList[l].nxtIsectAlongRingAndEdge1 = pseudoVtxListByRingAndEdge[i][j][k].nxtIsectAlongEdgeIn;
} else {
isectList[l].nxtIsectAlongRingAndEdge2 = pseudoVtxListByRingAndEdge[i][j][k].nxtIsectAlongEdgeIn;
}
}
}
}
}
var queue = [];
var i = 0;
for (var j = 0; j < numRings; j++) {
var leftIsect = i;
for (var k = 0; k < feature.geometry.coordinates[j].length - 1; k++) {
if (isectList[i].coord[0] < isectList[leftIsect].coord[0]) {
leftIsect = i;
}
i++;
}
var isectAfterLeftIsect = isectList[leftIsect].nxtIsectAlongRingAndEdge2;
for (var k = 0; k < isectList.length; k++) {
if (isectList[k].nxtIsectAlongRingAndEdge1 == leftIsect || isectList[k].nxtIsectAlongRingAndEdge2 == leftIsect) {
var isectBeforeLeftIsect = k;
break;
}
}
var windingAtIsect = isConvex(
[
isectList[isectBeforeLeftIsect].coord,
isectList[leftIsect].coord,
isectList[isectAfterLeftIsect].coord
],
true
) ? 1 : -1;
queue.push({ isect: leftIsect, parent: -1, winding: windingAtIsect });
}
queue.sort(function(a, b) {
return isectList[a.isect].coord > isectList[b.isect].coord ? -1 : 1;
});
var outputFeatureArray = [];
while (queue.length > 0) {
var popped = queue.pop();
var startIsect = popped.isect;
var currentOutputRingParent = popped.parent;
var currentOutputRingWinding = popped.winding;
var currentOutputRing = outputFeatureArray.length;
var currentOutputRingCoords = [isectList[startIsect].coord];
var currentIsect = startIsect;
if (isectList[startIsect].ringAndEdge1Walkable) {
var walkingRingAndEdge = isectList[startIsect].ringAndEdge1;
var nxtIsect = isectList[startIsect].nxtIsectAlongRingAndEdge1;
} else {
var walkingRingAndEdge = isectList[startIsect].ringAndEdge2;
var nxtIsect = isectList[startIsect].nxtIsectAlongRingAndEdge2;
}
while (!equalArrays2(isectList[startIsect].coord, isectList[nxtIsect].coord)) {
currentOutputRingCoords.push(isectList[nxtIsect].coord);
var nxtIsectInQueue = void 0;
for (var i = 0; i < queue.length; i++) {
if (queue[i].isect == nxtIsect) {
nxtIsectInQueue = i;
break;
}
}
if (nxtIsectInQueue != void 0) {
queue.splice(nxtIsectInQueue, 1);
}
if (equalArrays2(walkingRingAndEdge, isectList[nxtIsect].ringAndEdge1)) {
walkingRingAndEdge = isectList[nxtIsect].ringAndEdge2;
isectList[nxtIsect].ringAndEdge2Walkable = false;
if (isectList[nxtIsect].ringAndEdge1Walkable) {
var pushing = { isect: nxtIsect };
if (isConvex(
[
isectList[currentIsect].coord,
isectList[nxtIsect].coord,
isectList[isectList[nxtIsect].nxtIsectAlongRingAndEdge2].coord
],
currentOutputRingWinding == 1
)) {
pushing.parent = currentOutputRingParent;
pushing.winding = -currentOutputRingWinding;
} else {
pushing.parent = currentOutputRing;
pushing.winding = currentOutputRingWinding;
}
queue.push(pushing);
}
currentIsect = nxtIsect;
nxtIsect = isectList[nxtIsect].nxtIsectAlongRingAndEdge2;
} else {
walkingRingAndEdge = isectList[nxtIsect].ringAndEdge1;
isectList[nxtIsect].ringAndEdge1Walkable = false;
if (isectList[nxtIsect].ringAndEdge2Walkable) {
var pushing = { isect: nxtIsect };
if (isConvex(
[
isectList[currentIsect].coord,
isectList[nxtIsect].coord,
isectList[isectList[nxtIsect].nxtIsectAlongRingAndEdge1].coord
],
currentOutputRingWinding == 1
)) {
pushing.parent = currentOutputRingParent;
pushing.winding = -currentOutputRingWinding;
} else {
pushing.parent = currentOutputRing;
pushing.winding = currentOutputRingWinding;
}
queue.push(pushing);
}
currentIsect = nxtIsect;
nxtIsect = isectList[nxtIsect].nxtIsectAlongRingAndEdge1;
}
}
currentOutputRingCoords.push(isectList[nxtIsect].coord);
outputFeatureArray.push(
polygon([currentOutputRingCoords], {
index: currentOutputRing,
parent: currentOutputRingParent,
winding: currentOutputRingWinding,
netWinding: void 0
})
);
}
var output = featureCollection(outputFeatureArray);
determineParents();
setNetWinding();
function determineParents() {
var featuresWithoutParent = [];
for (var i2 = 0; i2 < output.features.length; i2++) {
if (output.features[i2].properties.parent == -1)
featuresWithoutParent.push(i2);
}
if (featuresWithoutParent.length > 1) {
for (var i2 = 0; i2 < featuresWithoutParent.length; i2++) {
var parent = -1;
var parentArea = Infinity;
for (var j2 = 0; j2 < output.features.length; j2++) {
if (featuresWithoutParent[i2] == j2) continue;
if (booleanPointInPolygon(
output.features[featuresWithoutParent[i2]].geometry.coordinates[0][0],
output.features[j2],
{ ignoreBoundary: true }
)) {
if (area(output.features[j2]) < parentArea) {
parent = j2;
}
}
}
output.features[featuresWithoutParent[i2]].properties.parent = parent;
}
}
}
function setNetWinding() {
for (var i2 = 0; i2 < output.features.length; i2++) {
if (output.features[i2].properties.parent == -1) {
var netWinding = output.features[i2].properties.winding;
output.features[i2].properties.netWinding = netWinding;
setNetWindingOfChildren(i2, netWinding);
}
}
}
function setNetWindingOfChildren(parent, ParentNetWinding) {
for (var i2 = 0; i2 < output.features.length; i2++) {
if (output.features[i2].properties.parent == parent) {
var netWinding = ParentNetWinding + output.features[i2].properties.winding;
output.features[i2].properties.netWinding = netWinding;
setNetWindingOfChildren(i2, netWinding);
}
}
}
return output;
}
var PseudoVtx = function(coord, param, ringAndEdgeIn, ringAndEdgeOut, nxtIsectAlongEdgeIn) {
this.coord = coord;
this.param = param;
this.ringAndEdgeIn = ringAndEdgeIn;
this.ringAndEdgeOut = ringAndEdgeOut;
this.nxtIsectAlongEdgeIn = nxtIsectAlongEdgeIn;
};
var Isect = function(coord, ringAndEdge1, ringAndEdge2, nxtIsectAlongRingAndEdge1, nxtIsectAlongRingAndEdge2, ringAndEdge1Walkable, ringAndEdge2Walkable) {
this.coord = coord;
this.ringAndEdge1 = ringAndEdge1;
this.ringAndEdge2 = ringAndEdge2;
this.nxtIsectAlongRingAndEdge1 = nxtIsectAlongRingAndEdge1;
this.nxtIsectAlongRingAndEdge2 = nxtIsectAlongRingAndEdge2;
this.ringAndEdge1Walkable = ringAndEdge1Walkable;
this.ringAndEdge2Walkable = ringAndEdge2Walkable;
};
function isConvex(pts, righthanded) {
if (typeof righthanded === "undefined") righthanded = true;
if (pts.length != 3)
throw new Error("This function requires an array of three points [x,y]");
var d = (pts[1][0] - pts[0][0]) * (pts[2][1] - pts[0][1]) - (pts[1][1] - pts[0][1]) * (pts[2][0] - pts[0][0]);
return d >= 0 == righthanded;
}
function windingOfRing(ring) {
var leftVtx = 0;
for (var i = 0; i < ring.length - 1; i++) {
if (ring[i][0] < ring[leftVtx][0]) leftVtx = i;
}
if (isConvex(
[
ring[modulo(leftVtx - 1, ring.length - 1)],
ring[leftVtx],
ring[modulo(leftVtx + 1, ring.length - 1)]
],
true
)) {
var winding = 1;
} else {
var winding = -1;
}
return winding;
}
function equalArrays2(array1, array2) {
if (!array1 || !array2) return false;
if (array1.length != array2.length) return false;
for (var i = 0, l = array1.length; i < l; i++) {
if (array1[i] instanceof Array && array2[i] instanceof Array) {
if (!equalArrays2(array1[i], array2[i])) return false;
} else if (array1[i] != array2[i]) {
return false;
}
}
return true;
}
function modulo(n, m) {
return (n % m + m) % m;
}
function isUnique(array) {
var u = {};
var isUnique2 = 1;
for (var i = 0, l = array.length; i < l; ++i) {
if (Object.prototype.hasOwnProperty.call(u, array[i])) {
isUnique2 = 0;
break;
}
u[array[i]] = 1;
}
return isUnique2;
}
// index.ts
function unkinkPolygon(geojson) {
var features = [];
flattenEach(geojson, function(feature) {
if (feature.geometry.type !== "Polygon") return;
featureEach(simplepolygon(feature), function(poly) {
features.push(polygon2(poly.geometry.coordinates, feature.properties));
});
});
return featureCollection2(features);
}
var turf_unkink_polygon_default = unkinkPolygon;
export {
turf_unkink_polygon_default as default,
unkinkPolygon
};
//# sourceMappingURL=index.js.map