UNPKG

@turf/unkink-polygon

Version:

turf unkink-polygon module

579 lines (571 loc) 19.7 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }// index.ts var _meta = require('@turf/meta'); var _helpers = require('@turf/helpers'); // lib/geojson-polygon-self-intersections.js var _rbush = require('rbush'); var _rbush2 = _interopRequireDefault(_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 (0, _rbush2.default)(); 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 var _area = require('@turf/area'); var _booleanpointinpolygon = require('@turf/boolean-point-in-polygon'); 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( _helpers.polygon.call(void 0, [feature.geometry.coordinates[i]], { parent: -1, winding: windingOfRing(feature.geometry.coordinates[i]) }) ); } var output = _helpers.featureCollection.call(void 0, 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 (0, _rbush2.default)(); 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( _helpers.polygon.call(void 0, [currentOutputRingCoords], { index: currentOutputRing, parent: currentOutputRingParent, winding: currentOutputRingWinding, netWinding: void 0 }) ); } var output = _helpers.featureCollection.call(void 0, 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.booleanPointInPolygon.call(void 0, output.features[featuresWithoutParent[i2]].geometry.coordinates[0][0], output.features[j2], { ignoreBoundary: true } )) { if (_area.area.call(void 0, 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 = []; _meta.flattenEach.call(void 0, geojson, function(feature) { if (feature.geometry.type !== "Polygon") return; _meta.featureEach.call(void 0, simplepolygon(feature), function(poly) { features.push(_helpers.polygon.call(void 0, poly.geometry.coordinates, feature.properties)); }); }); return _helpers.featureCollection.call(void 0, features); } var turf_unkink_polygon_default = unkinkPolygon; exports.default = turf_unkink_polygon_default; exports.unkinkPolygon = unkinkPolygon; //# sourceMappingURL=index.cjs.map