160 lines
5.5 KiB
JavaScript
Executable File
160 lines
5.5 KiB
JavaScript
Executable File
import { feature } from "@turf/helpers";
|
|
import { getCoords, getType } from "@turf/invariant";
|
|
// To-Do => Improve Typescript GeoJSON handling
|
|
/**
|
|
* Removes redundant coordinates from any GeoJSON Geometry.
|
|
*
|
|
* @name cleanCoords
|
|
* @param {Geometry|Feature} geojson Feature or Geometry
|
|
* @param {Object} [options={}] Optional parameters
|
|
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated
|
|
* @returns {Geometry|Feature} the cleaned input Feature/Geometry
|
|
* @example
|
|
* var line = turf.lineString([[0, 0], [0, 2], [0, 5], [0, 8], [0, 8], [0, 10]]);
|
|
* var multiPoint = turf.multiPoint([[0, 0], [0, 0], [2, 2]]);
|
|
*
|
|
* turf.cleanCoords(line).geometry.coordinates;
|
|
* //= [[0, 0], [0, 10]]
|
|
*
|
|
* turf.cleanCoords(multiPoint).geometry.coordinates;
|
|
* //= [[0, 0], [2, 2]]
|
|
*/
|
|
function cleanCoords(geojson, options) {
|
|
if (options === void 0) { options = {}; }
|
|
// Backwards compatible with v4.0
|
|
var mutate = typeof options === "object" ? options.mutate : options;
|
|
if (!geojson)
|
|
throw new Error("geojson is required");
|
|
var type = getType(geojson);
|
|
// Store new "clean" points in this Array
|
|
var newCoords = [];
|
|
switch (type) {
|
|
case "LineString":
|
|
newCoords = cleanLine(geojson);
|
|
break;
|
|
case "MultiLineString":
|
|
case "Polygon":
|
|
getCoords(geojson).forEach(function (line) {
|
|
newCoords.push(cleanLine(line));
|
|
});
|
|
break;
|
|
case "MultiPolygon":
|
|
getCoords(geojson).forEach(function (polygons) {
|
|
var polyPoints = [];
|
|
polygons.forEach(function (ring) {
|
|
polyPoints.push(cleanLine(ring));
|
|
});
|
|
newCoords.push(polyPoints);
|
|
});
|
|
break;
|
|
case "Point":
|
|
return geojson;
|
|
case "MultiPoint":
|
|
var existing = {};
|
|
getCoords(geojson).forEach(function (coord) {
|
|
var key = coord.join("-");
|
|
if (!Object.prototype.hasOwnProperty.call(existing, key)) {
|
|
newCoords.push(coord);
|
|
existing[key] = true;
|
|
}
|
|
});
|
|
break;
|
|
default:
|
|
throw new Error(type + " geometry not supported");
|
|
}
|
|
// Support input mutation
|
|
if (geojson.coordinates) {
|
|
if (mutate === true) {
|
|
geojson.coordinates = newCoords;
|
|
return geojson;
|
|
}
|
|
return { type: type, coordinates: newCoords };
|
|
}
|
|
else {
|
|
if (mutate === true) {
|
|
geojson.geometry.coordinates = newCoords;
|
|
return geojson;
|
|
}
|
|
return feature({ type: type, coordinates: newCoords }, geojson.properties, {
|
|
bbox: geojson.bbox,
|
|
id: geojson.id,
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Clean Coords
|
|
*
|
|
* @private
|
|
* @param {Array<number>|LineString} line Line
|
|
* @returns {Array<number>} Cleaned coordinates
|
|
*/
|
|
function cleanLine(line) {
|
|
var points = getCoords(line);
|
|
// handle "clean" segment
|
|
if (points.length === 2 && !equals(points[0], points[1]))
|
|
return points;
|
|
var newPoints = [];
|
|
var secondToLast = points.length - 1;
|
|
var newPointsLength = newPoints.length;
|
|
newPoints.push(points[0]);
|
|
for (var i = 1; i < secondToLast; i++) {
|
|
var prevAddedPoint = newPoints[newPoints.length - 1];
|
|
if (points[i][0] === prevAddedPoint[0] &&
|
|
points[i][1] === prevAddedPoint[1])
|
|
continue;
|
|
else {
|
|
newPoints.push(points[i]);
|
|
newPointsLength = newPoints.length;
|
|
if (newPointsLength > 2) {
|
|
if (isPointOnLineSegment(newPoints[newPointsLength - 3], newPoints[newPointsLength - 1], newPoints[newPointsLength - 2]))
|
|
newPoints.splice(newPoints.length - 2, 1);
|
|
}
|
|
}
|
|
}
|
|
newPoints.push(points[points.length - 1]);
|
|
newPointsLength = newPoints.length;
|
|
if (equals(points[0], points[points.length - 1]) && newPointsLength < 4)
|
|
throw new Error("invalid polygon");
|
|
if (isPointOnLineSegment(newPoints[newPointsLength - 3], newPoints[newPointsLength - 1], newPoints[newPointsLength - 2]))
|
|
newPoints.splice(newPoints.length - 2, 1);
|
|
return newPoints;
|
|
}
|
|
/**
|
|
* Compares two points and returns if they are equals
|
|
*
|
|
* @private
|
|
* @param {Position} pt1 point
|
|
* @param {Position} pt2 point
|
|
* @returns {boolean} true if they are equals
|
|
*/
|
|
function equals(pt1, pt2) {
|
|
return pt1[0] === pt2[0] && pt1[1] === pt2[1];
|
|
}
|
|
/**
|
|
* Returns if `point` is on the segment between `start` and `end`.
|
|
* Borrowed from `@turf/boolean-point-on-line` to speed up the evaluation (instead of using the module as dependency)
|
|
*
|
|
* @private
|
|
* @param {Position} start coord pair of start of line
|
|
* @param {Position} end coord pair of end of line
|
|
* @param {Position} point coord pair of point to check
|
|
* @returns {boolean} true/false
|
|
*/
|
|
function isPointOnLineSegment(start, end, point) {
|
|
var x = point[0], y = point[1];
|
|
var startX = start[0], startY = start[1];
|
|
var endX = end[0], endY = end[1];
|
|
var dxc = x - startX;
|
|
var dyc = y - startY;
|
|
var dxl = endX - startX;
|
|
var dyl = endY - startY;
|
|
var cross = dxc * dyl - dyc * dxl;
|
|
if (cross !== 0)
|
|
return false;
|
|
else if (Math.abs(dxl) >= Math.abs(dyl))
|
|
return dxl > 0 ? startX <= x && x <= endX : endX <= x && x <= startX;
|
|
else
|
|
return dyl > 0 ? startY <= y && y <= endY : endY <= y && y <= startY;
|
|
}
|
|
export default cleanCoords;
|