@cogic/annotorious
Version:
A JavaScript image annotation library
177 lines (146 loc) • 4.84 kB
JavaScript
/**
* Computes the area of the polygon defined by
* the given conrner points.
* @param {Array} points
* @returns {number} the area
*/
export const polygonArea = points => {
let area = 0;
let j = points.length - 1;
for (let i=0; i < points.length; i++) {
area += (points[j][0] + points[i][0]) * (points[j][1] - points[i][1]);
j = i;
}
return Math.abs(0.5 * area);
}
/**
* Hit test: checks if this point is inside the circle.
* @param {Array} point the point [x, y]
* @param {number} cx circle center x
* @param {number} cy circle center y
* @param {number} r circle radius
* @returns {boolean}
*/
export const pointInCircle = (point, cx, cy, r) => {
const dx = point[0] - cx;
const dy = point[1] - cy;
const d = Math.sqrt(dx * dx + dy * dy);
return d <= r;
}
/**
* Hit test: checks if this point is inside the ellipse.
* Cf. https://github.com/w8r/point-in-ellipse
* @param {Array} point the point [x, y]
* @param {number} cx ellipse center x
* @param {number} cy ellipse center y
* @param {number} rx ellipse x radius
* @param {number} ry ellipse y radius
* @param {number=} rotation ellipse rotation
* @returns {boolean}
*/
export const pointInEllipse = (point, cx, cy, rx, ry, rotation) => {
const rot = rotation || 0;
const cos = Math.cos(rot);
const sin = Math.sin(rot);
const dx = point[0] - cx;
const dy = point[1] - cy;
const tdx = cos * dx + sin * dy;
const tdy = sin * dx - cos * dy;
return (tdx * tdx) / (rx * rx) + (tdy * tdy) / (ry * ry) <= 1;
}
/**
* Hit test: checks if this point is inside the polygon.
* @param {Array} xy the point [x, y]
* @param {Array<number>} points polygon corner points
* @param {boolean} [noneZeroMode=true] whether to use None Zero Mode
* @returns {boolean}
*/
export const pointInPolygon = (xy, points, noneZeroMode = true) => {
// Algorithm checks, if xy is in Polygon
// algorithm based on
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html/pnpoly.html
// and https://www.cnblogs.com/guogangj/p/5127527.html
const x = xy[0];
const y = xy[1];
let oddNodes = false;
let zeroState = 0;
for (let i=0, j=points.length-1; i < points.length; j=i++) {
const xi = points[i][0], yi = points[i][1];
const xj = points[j][0], yj = points[j][1];
const intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) {
oddNodes = !oddNodes;
if (yi > yj) {
zeroState++;
} else {
zeroState--;
}
}
}
return noneZeroMode ? zeroState != 0 : oddNodes;
}
/**
* Checks if polygon A is contained fully inside polygon B.
* @param {Array<Array<number>>} polygonA array of points [x, y]
* @param {Array<Array<number>>} polygonB array of points [x, y]
* @returns {boolean}
*/
export const polygonInPolygon = (polygonA, polygonB) => {
for (let point of polygonA) {
if (!pointInPolygon(point, polygonB))
return false
}
return true;
}
/**
* Hit test: checks if this point is inside the line.
* @param {Array} xy the point [x, y]
* @param number x1 line start x
* @param number y1 line start y
* @param number x2 line end x
* @param number y2 line end y
* @param number buffer around the line
* @returns {boolean}
*/
export const pointInLine = (xy, x1, y1, x2, y2, buffer) => {
const x = xy[0];
const y = xy[1];
const dx = x2 - x1;
const dy = y2 - y1;
const d = Math.sqrt(dx * dx + dy * dy);
const cross = Math.abs((x - x1) * dy - (y - y1) * dx);
const dist = cross / d;
return dist <= buffer;
}
/**
* A utility helper that parses an SVG path into
* a list of polygons.
* @param {SVGElement} path the SVG path
* @returns {Array<Array<Array<number>>>} list of polygons
*/
export const svgPathToPolygons = path => {
const commands = path.getAttribute('d')
.split(/(?=M|m|L|l|H|h|V|v|Z|z)/g)
.map(str => str.trim());
const polygons = [];
let points = [];
for (let cmd of commands) {
const op = cmd.substring(0, 1);
if (op.toLowerCase() === 'z') {
polygons.push([...points]);
points = [];
} else {
const xy = cmd.substring(1).split(' ')
.map(str => parseFloat(str.trim()));
// Uppercase ops are absolute coords -> transform
const isUppercase = op === op.toUpperCase();
const x = isUppercase ? xy[0] : xy[0] + points[points.length - 1][0];
const y = isUppercase ? xy[1] : xy[1] + points[points.length - 1][1];
points.push([x, y]);
}
}
if (points.length > 0) // Unclosed path - close for area computation
polygons.push([...points]);
return polygons;
}