diff --git a/src/utils.js b/src/utils.js index 32b882a2e..aac771037 100644 --- a/src/utils.js +++ b/src/utils.js @@ -126,4 +126,5 @@ export default { }; export { default as slugify } from './utils/slugify'; +export { default as topology } from './utils/topology'; export { default as exportToFile } from './utils/exporttofile'; diff --git a/src/utils/topology.js b/src/utils/topology.js index f4920ef36..f8022665e 100644 --- a/src/utils/topology.js +++ b/src/utils/topology.js @@ -152,6 +152,140 @@ const topology = { default: valid = true; } return valid; + }, + + /** + * Densifies a geometry by adding points along segments. + * @param {any} geom The geometry to densify + * @param {double} multiple Higher value means more breakpoints + * @returns {any} geom The densified geometry + */ + densify: function densify(geom, multiple = 1) { + function densifyGeom(geometry, mult) { + let maxLength = 100; + const lineCoords = []; + const length = geometry.getLength(); + if (length < 100) { + maxLength = 5 / mult; + } else if (length < 1000) { + maxLength = 25 / mult; + } else if (length < 10000) { + maxLength = 50 / mult; + } + if (maxLength < 1) { maxLength = 1; } + const coords = geometry.getCoordinates(); + lineCoords.push(coords[0]); + geometry.forEachSegment((start, end) => { + const segment = new LineString([start, end]); + const segmentLength = segment.getLength(); + const splits = Math.ceil(segmentLength / maxLength); + for (let i = 1; i < splits; i += 1) { + const fraction = i / splits; + const pt = segment.getCoordinateAt(fraction); + lineCoords.push(pt); + } + lineCoords.push(end); + }); + return lineCoords; + } + + const densified = geom.clone(); + const densifiedCoords = densifyGeom(geom, multiple); + if (densifiedCoords) { + densified.setCoordinates(densifiedCoords); + } + return densified; + }, + + /** + * Simplifies a line geometry. + * @param {any} geom The line geometry to simplify + * @param {double} tolerance + * @returns {any} geom The simplified geometry + */ + simplify: function simplify(geom, tolerance = 1) { + function sqSegDist(p, p1, p2) { + const x0 = p[0]; + const x2 = p2[0]; + const y0 = p[1]; + const y2 = p2[1]; + const z0 = p[2] || 0; + const z2 = p2[2] || 0; + let x1 = p1[0]; + let y1 = p1[1]; + let z1 = p1[2] || 0; + let dx = x2 - x1; + let dy = y2 - y1; + let dz = z2 - z1; + if (dx !== 0 || dy !== 0 || dz !== 0) { + const t = ((x0 - x1) * dx + (y0 - y1) * dy + (z0 - z1) * dz) / (dx * dx + dy * dy + dz * dz); + if (t > 1) { + x1 = x2; + y1 = y2; + z1 = z2; + } else if (t > 0) { + x1 += dx * t; + y1 += dy * t; + z1 += dz * t; + } + } + dx = x0 - x1; + dy = y0 - y1; + dz = z0 - z1; + return dx * dx + dy * dy + dz * dz; + } + + function simplifyDouglasPeucker(geometry, sqTolerance) { + const points = geometry.getCoordinates(); + const len = points.length; + const pArr = new Array(len); + const stack = []; + const newPoints = []; + let first = 0; + let last = len - 1; + let i; + let maxSqDist; + let sqDist; + let index; + pArr[first] = 1; + pArr[last] = 1; + + while (last) { + maxSqDist = 0; + for (i = first + 1; i < last; i += 1) { + sqDist = sqSegDist(points[i], points[first], points[last]); + if (sqDist > maxSqDist) { + index = i; + maxSqDist = sqDist; + } + } + if (maxSqDist > sqTolerance) { + pArr[index] = 1; + stack.push(first, index, index, last); + } + last = stack.pop(); + first = stack.pop(); + } + for (i = 0; i < len; i += 1) { + if (pArr[i]) { + newPoints.push(points[i]); + } + } + return newPoints; + } + + function simplifyGeom(geometry, tol) { + const sqTolerance = tol * tol || 1; + const points = simplifyDouglasPeucker(geometry, sqTolerance); + return points; + } + + const simplified = geom.clone(); + const simplifiedCoords = simplifyGeom(geom, tolerance); + if (simplifiedCoords) { + simplified.setCoordinates(simplifiedCoords); + } + return simplified; } };