@allmaps/stdlib
Version:
Allmaps Standard Library
428 lines (427 loc) • 13.6 kB
JavaScript
import { rewind } from '@turf/rewind';
// Assert
export function isPoint(input) {
return (Array.isArray(input) &&
input.length >= 2 &&
typeof input[0] === 'number' &&
typeof input[1] === 'number');
}
export function isLineString(input) {
return Array.isArray(input) && input.every(isPoint);
}
export function isRing(input) {
return Array.isArray(input) && input.every(isPoint);
}
export function isPolygon(input) {
return Array.isArray(input) && input.every(isRing);
}
export function isMultiPoint(input) {
return Array.isArray(input) && input.every(isPoint);
}
export function isMultiLineString(input) {
return Array.isArray(input) && input.every(isLineString);
}
export function isMultiPolygon(input) {
return Array.isArray(input) && input.every(isPolygon);
}
export function isGeometry(input) {
return (isPoint(input) ||
isLineString(input) ||
isPolygon(input) ||
isMultiPoint(input) ||
isMultiLineString(input) ||
isMultiPolygon(input));
}
// Close
export function closeRing(ring) {
return [...ring, ring[0]];
}
export function uncloseRing(ring) {
ring.splice(-1);
return ring;
}
export function closePolygon(polygon) {
return polygon.map((ring) => closeRing(ring));
}
export function unclosePolygon(polygon) {
return polygon.map((ring) => uncloseRing(ring));
}
export function closeMultiPolygon(multiPolygon) {
return multiPolygon.map((polygon) => closePolygon(polygon));
}
export function uncloseMultiPolygon(multiPolygon) {
return multiPolygon.map((polygon) => unclosePolygon(polygon));
}
// Conform
export function conformLineString(lineString) {
// Filter out repeated points
lineString = lineString.filter(function (point, i, originalLineString) {
return i === 0 || !isEqualPoint(point, originalLineString[i - 1]);
});
if (lineString.length < 2) {
throw new Error('LineString should contain at least 2 points');
}
return lineString;
}
export function conformRing(ring) {
// Filter out repeated points
ring = ring.filter(function (point, i, originalRing) {
return i === 0 || !isEqualPoint(point, originalRing[i - 1]);
});
// Remove last point if input is closed ring
if (isClosed(ring)) {
uncloseRing(ring);
}
if (ring.length < 3) {
throw new Error('Ring should contain at least 3 points');
}
return ring;
}
export function conformPolygon(polygon) {
return polygon.map((ring) => {
return conformRing(ring);
});
}
export function conformMultiLineString(multiLineString) {
return multiLineString.map((lineString) => conformLineString(lineString));
}
export function conformMultiPolygon(multiPolygon) {
return multiPolygon.map((polygon) => conformPolygon(polygon));
}
// Convert to GeoJSON
export function pointToGeojsonPoint(point) {
return {
type: 'Point',
coordinates: point
};
}
export function lineStringToGeojsonLineString(lineString) {
return {
type: 'LineString',
coordinates: lineString
};
}
export function ringToGeojsonPolygon(ring, close = true) {
const geometry = {
type: 'Polygon',
coordinates: close ? [closeRing(ring)] : [ring]
};
return rewind(geometry);
}
export function polygonToGeojsonPolygon(polygon, close = true) {
const geometry = {
type: 'Polygon',
coordinates: close ? closePolygon(polygon) : polygon
};
return rewind(geometry);
}
export function multiPointToGeojsonMultiPoint(multiPoint) {
return {
type: 'MultiPoint',
coordinates: multiPoint
};
}
export function multiLineStringToGeojsonMultiLineString(multiLineString) {
return {
type: 'MultiLineString',
coordinates: multiLineString
};
}
export function multiPolygonToGeojsonMultiPolygon(multiPolygon, close = true) {
const geometry = {
type: 'MultiPolygon',
coordinates: close ? closeMultiPolygon(multiPolygon) : multiPolygon
};
return rewind(geometry);
}
/**
* Converts a Geometry to a GeoJSON Geometry
* @param geometry - Geometry
* @returns GeoJSON Geometry
*/
export function geometryToGeojsonGeometry(geometry, options) {
if (!options || !options.isMultiGeometry) {
if (isPoint(geometry)) {
return pointToGeojsonPoint(geometry);
}
else if (isLineString(geometry)) {
return lineStringToGeojsonLineString(geometry);
}
else if (isPolygon(geometry)) {
return polygonToGeojsonPolygon(geometry);
}
else {
throw new Error('Geometry type not supported');
}
}
else {
if (isMultiPoint(geometry)) {
return multiPointToGeojsonMultiPoint(geometry);
}
else if (isMultiLineString(geometry)) {
return multiLineStringToGeojsonMultiLineString(geometry);
}
else if (isMultiPolygon(geometry)) {
return multiPolygonToGeojsonMultiPolygon(geometry);
}
else {
throw new Error('Geometry type not supported');
}
}
}
// Convert to SVG
export function pointToSvgCircle(point) {
return {
type: 'circle',
coordinates: point
};
}
export function lineStringToSvgPolyLine(lineString) {
return {
type: 'polyline',
coordinates: lineString
};
}
export function polygonToSvgPolygon(polygon) {
return {
type: 'polygon',
coordinates: polygon[0]
};
}
/**
* Converts a Geometry to a SVG Geometry
* @param geometry - Geometry
* @returns SVG Geometry
* }}
*/
export function geometryToSvgGeometry(geometry) {
if (isPoint(geometry)) {
return pointToSvgCircle(geometry);
}
else if (isLineString(geometry)) {
return lineStringToSvgPolyLine(geometry);
}
else if (isPolygon(geometry)) {
return polygonToSvgPolygon(geometry);
}
else {
throw new Error(`Unsupported GeoJSON Geometry`);
}
}
// Check
export function isClosed(input) {
return (Array.isArray(input) &&
input.length >= 2 &&
isEqualPoint(input[0], input[input.length - 1]));
}
export function isEqualPoint(point0, point1) {
if (point0 === point1)
return true;
if (point0 === null || point1 === null)
return false;
return point0[0] === point1[0] && point0[1] === point1[1];
}
export function isEqualPointArray(pointArray0, pointArray1) {
if (pointArray0 === pointArray1)
return true;
if (!pointArray0 || !pointArray1)
return false;
if (pointArray0.length !== pointArray1.length)
return false;
for (let i = 0; i < pointArray0.length; ++i) {
if (isEqualPoint(pointArray0[i], pointArray1[i]))
return false;
}
return true;
}
export function isEqualPointArrayArray(pointArrayArray0, pointArrayArray1) {
if (pointArrayArray0 === pointArrayArray1)
return true;
if (!pointArrayArray0 || !pointArrayArray1)
return false;
if (pointArrayArray0.length !== pointArrayArray1.length)
return false;
for (let i = 0; i < pointArrayArray0.length; ++i) {
if (isEqualPointArray(pointArrayArray0[i], pointArrayArray1[i]))
return false;
}
return true;
}
// Split, combine, shift, flip
export function pointsAndPointsToLines(points0, points1) {
if (points0.length !== points1.length)
throw new Error('Point arrays should be of same length');
return points0.map((point0, index) => [point0, points1[index]]);
}
export function lineStringToLines(lineString) {
return lineString.reduce((accumulator, point, index) => [
...accumulator,
[point, lineString[(index + 1) % lineString.length]]
], []);
}
export function pointToPixel(point, translate = [0, 0]) {
return point.map((coordinate, index) => {
return Math.floor(coordinate + translate[index]);
});
}
export function pixelToIntArrayIndex(pixel, size, channels, flipY = false) {
const column = pixel[0];
const row = flipY ? size[1] - 1 - pixel[1] : pixel[1];
return (row * size[0] + column) * channels;
}
export function flipX(point) {
return [-point[0], point[1]];
}
export function flipY(point) {
return [point[0], -point[1]];
}
// Mix
export function mixNumbers(number0, number1, t) {
return number0 * (1 - t) + number1 * t;
}
export function mixPoints(point0, point1, t) {
return [
mixNumbers(point0[0], point1[0], t),
mixNumbers(point0[1], point1[1], t)
];
}
export function mixLineStrings(lineString0, lineString1, t) {
return lineString0.map((point, index) => {
return mixPoints(point, lineString1[index], t);
});
}
// Compute
export function midPoint(...points) {
const result = [0, 0];
for (let i = 0; i < points.length; i++) {
result[0] += points[i][0];
result[1] += points[i][1];
}
result[0] = result[0] / points.length;
result[1] = result[1] / points.length;
return result;
}
// Return angle of line (in radians, signed)
export function lineAngle(line) {
return Math.atan2(line[1][1] - line[0][1], line[1][0] - line[0][0]);
}
// Return the next point starting from a point going a certian distance in a certain direction
export function stepDistanceAngle(point, dist, angle) {
return [point[0] + Math.cos(angle) * dist, point[1] + Math.sin(angle) * dist];
}
export function distance(from, to) {
if (isLineString(from) && from.length === 2) {
return distance(from[0], from[1]);
}
else if (isPoint(from) && to === undefined) {
return distance(from, [0, 0]);
}
else if (isPoint(from) && isPoint(to)) {
return Math.sqrt(squaredDistance(from, to));
}
else {
throw new Error('Input type not supported');
}
}
export function squaredDistance(from, to) {
if (isLineString(from) && from.length === 2) {
return squaredDistance(from[0], from[1]);
}
else if (isPoint(from) && to === undefined) {
return squaredDistance(from, [0, 0]);
}
else if (isPoint(from) && isPoint(to)) {
return (to[0] - from[0]) ** 2 + (to[1] - from[1]) ** 2;
}
else {
throw new Error('Input type not supported');
}
}
export function rms(from, to) {
if (from.length !== to.length) {
throw new Error('Arrays need to be of same length');
}
const squaredDistances = from.map((fromPoint, index) => squaredDistance(fromPoint, to[index]));
const meanSquaredDistances = squaredDistances.reduce((sum, squaredDistace) => sum + squaredDistace, 0) /
squaredDistances.length;
const rootMeanSquaredDistances = Math.sqrt(meanSquaredDistances);
return rootMeanSquaredDistances;
}
export function triangleArea(triangle) {
return (0.5 *
Math.abs(triangle[0][0] * (triangle[1][1] - triangle[2][1]) +
triangle[1][0] * (triangle[2][1] - triangle[0][1]) +
triangle[2][0] * (triangle[0][1] - triangle[1][1])));
}
export function invertPoint(point) {
return [-point[0], -point[1]];
}
export function invertPoints(points) {
return points.map((point) => invertPoint(point));
}
export function scalePoint(point, scale) {
if (scale === 1) {
return point;
}
return [point[0] * scale, point[1] * scale];
}
export function scalePoints(points, scale) {
if (scale === 1) {
return points;
}
return points.map((point) => scalePoint(point, scale));
}
export function translatePoint(point, translationPoint, addOrSubstract = 'add') {
if (addOrSubstract === 'add') {
return [point[0] + translationPoint[0], point[1] + translationPoint[1]];
}
else {
return [point[0] - translationPoint[0], point[1] - translationPoint[1]];
}
}
export function translatePoints(points, point, addOrSubstract = 'add') {
if (isEqualPoint(point, [0, 0])) {
return points;
}
return points.map((p) => translatePoint(p, point, addOrSubstract));
}
export function rotatePoint(point, angle = 0, anchor = undefined, cosAngle, sinAngle) {
if (angle === 0 || angle === undefined) {
return point;
}
if (anchor) {
return translatePoint(rotatePoint(translatePoint(point, anchor, 'substract'), angle, undefined, cosAngle, sinAngle), anchor);
}
else {
cosAngle = cosAngle || Math.cos(angle);
sinAngle = sinAngle || Math.sin(angle);
return [
point[0] * cosAngle - point[1] * sinAngle,
point[0] * sinAngle + point[1] * cosAngle
];
}
}
export function rotatePoints(points, angle = 0, anchor = undefined, cosAngle, sinAngle) {
if (angle === 0 || angle === undefined) {
return points;
}
cosAngle = cosAngle || Math.cos(angle);
sinAngle = sinAngle || Math.sin(angle);
return points.map((point) => rotatePoint(point, angle, anchor, cosAngle, sinAngle));
}
export function triangleAngles(triangle) {
return [
threePointsToAngle(triangle[0], triangle[1], triangle[2]),
threePointsToAngle(triangle[1], triangle[2], triangle[0]),
threePointsToAngle(triangle[2], triangle[0], triangle[1])
];
}
/**
* Return angle alpha made at point A by points B and C
*/
export function threePointsToAngle(pointA, pointB, pointC) {
const AB = distance(pointA, pointB);
const BC = distance(pointB, pointC);
const AC = distance(pointA, pointC);
return Math.acos((AB ** 2 + AC ** 2 - BC ** 2) / (2 * AB * AC));
}