buildJstsPolygon = {
function splitRing(ring) {
const splitpoints = createPointHashSet(ring.length);
{
const allPoints = createPointHashSet(ring.length);
for (let coord of ring.slice(0, -1)) {
if (allPoints.has(coord)) splitpoints.add(coord);
allPoints.add(coord);
}
}
if (splitpoints.values().length == 0) return [ring];
splitpoints.add(ring[0]);
let segment = [];
const segments = [segment];
const ringSegments = [];
const lastIndex = ring.length - 1;
for (let i = 0; i < ring.length; i++) {
const coord = ring[i];
segment.push(coord);
if (splitpoints.has(coord)) {
if (segment.length > 1) {
const ringStartIdx = segments.findLastIndex(s => pointsEqual(s[0], coord));
if (ringStartIdx >= 0) ringSegments.push(segments.splice(ringStartIdx));
}
if (i == 0 || i == lastIndex) continue;
segment = [coord];
segments.push(segment);
}
}
return ringSegments.map(rs => [rs[0][0], ...rs.flatMap(s => s.slice(1))]);
}
// This function converts the output of tracedPolygons (a big ol' pile of rings) into valid GeoJSON (multipolygons with holes).
function buildJstsPolygon(ringCoords, ignoreHoles = false) {
const { Orientation, Coordinate } = jsts;
ringCoords = ringCoords.flatMap(ring => splitRing(ring));
const polygonMeta = ringCoords.map(rc => {
const coords = rc.map(c => new Coordinate(...c));
const group = Orientation.isCCW(coords) ? "shellMeta" : "holeMeta";
const ring = jstsFactory.createLinearRing(coords);
const polygon = jstsFactory.createPolygon(ring);
if (ignoreHoles && group == "holeMeta") return undefined;
return {
ring,
polygon,
group,
bbox: Bbox.fromPoints(rc),
area: polygon.getArea()
};
});
polygonMeta.sort((a,b) => a.area - b.area);
let { shellMeta, holeMeta } = _.groupBy(polygonMeta, rm => rm.group);
shellMeta ??= [];
holeMeta ??= [];
shellMeta.forEach(shell => shell.holeRings = []);
if (!ignoreHoles) {
for (let hole of holeMeta) {
let shell;
if (shellMeta.length == 1) {
shell = shellMeta[0];
}
else {
const candidates = shellMeta.filter(shell => shell.area > hole.area && Bbox.covers(shell.bbox, hole.bbox));
if (candidates.length == 1) {
shell = candidates[0];
}
else {
shell = candidates.find(shell => shell.polygon.covers(hole.polygon));
}
}
if (!shell) {
debugger;
throw "couldn't find shell for hole";
}
shell.holeRings.push(hole.ring);
}
}
const islands = [];
for (let shell of shellMeta) {
const holeRings = shell.holeRings;
if (holeRings.length == 0) {
islands.push(shell.polygon);
}
else {
islands.push(jstsFactory.createPolygon(shell.ring, shell.holeRings));
}
}
const result = jstsFactory.createMultiPolygon(islands);
if (islands.length > 1 || islands[0].getNumInteriorRing() > 1) {
return result.buffer(0);
}
return result;
}
return buildJstsPolygon
}