Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/geometry/polygon.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,48 @@
import { Point } from "./point";
import { getCentroid, getMinMaxX, getMinMaxY, isPointInX, isPointInY } from "./polygonUtils";
import { MinMax } from "./minMax";

/** A polygon, composed of several Points. */
export class Polygon extends Array<Point> {

constructor(...args: Point[]) {
super(...args);
}

/**
* Get the central point (centroid) of the polygon.
*/
public getCentroid(): Point {
return getCentroid(this);
}

/**
* Get the maximum and minimum X coordinates of the polygon.
*/
public getMinMaxX(): MinMax {
return getMinMaxX(this);
}

/**
* Get the maximum and minimum Y coordinates of the polygon.
*/
public getMinMaxY(): MinMax {
return getMinMaxY(this);
}

/**
* Determine if a Point is within the Polygon's X axis (same column).
*/
public isPointInX(point: Point): boolean {
const xCoords = this.getMinMaxX();
return isPointInX(point, xCoords.min, xCoords.max);
}

/**
* Determine if a Point is within the Polygon's Y axis (same line).
*/
public isPointInY(point: Point): boolean {
const yCoords = this.getMinMaxY();
return isPointInY(point, yCoords.min, yCoords.max);
}
}
17 changes: 8 additions & 9 deletions src/geometry/polygonUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { MinMax } from "./minMax";
import { Point } from "./point";
import { Polygon } from "./polygon";

/**
* Get the central point (centroid) given a list of points.
Expand Down Expand Up @@ -38,7 +37,7 @@ export function getMinMaxY(vertices: Array<Point>): MinMax {
/**
* Determine if a Point is within a Polygon's Y axis.
*/
export function isPointInPolygonY(centroid: Point, polygon: Polygon): boolean {
export function isPointInPolygonY(centroid: Point, polygon: Array<Point>): boolean {
const yCoords = getMinMaxY(polygon);
return isPointInY(centroid, yCoords.min, yCoords.max);
}
Expand All @@ -48,7 +47,7 @@ export function isPointInPolygonY(centroid: Point, polygon: Polygon): boolean {
*
* Can be used to order (sort) words in the same column.
*/
export function relativeY(polygon: Polygon): number {
export function relativeY(polygon: Array<Point>): number {
const sum: number = polygon
.map((point) => point[1])
.reduce((prev, cur) => prev + cur);
Expand Down Expand Up @@ -77,7 +76,7 @@ export function getMinMaxX(vertices: Array<Point>): MinMax {
/**
* Determine if a Point is within a Polygon's X axis.
*/
export function isPointInPolygonX(centroid: Point, polygon: Polygon): boolean {
export function isPointInPolygonX(centroid: Point, polygon: Array<Point>): boolean {
const xCoords = getMinMaxX(polygon);
return isPointInX(centroid, xCoords.min, xCoords.max);
}
Expand All @@ -87,14 +86,14 @@ export function isPointInPolygonX(centroid: Point, polygon: Polygon): boolean {
*
* Can be used to order (sort) words in the same line.
*/
export function relativeX(polygon: Polygon): number {
export function relativeX(polygon: Array<Point>): number {
const sum: number = polygon
.map((point) => point[0])
.reduce((prev, cur) => prev + cur);
return polygon.length * sum;
}

export function getMinYCoordinate(polygon: Polygon): number {
export function getMinYCoordinate(polygon: Array<Point>): number {
return polygon.sort((point1, point2) => {
if (point1[1] < point2[1]) {
return -1;
Expand All @@ -105,7 +104,7 @@ export function getMinYCoordinate(polygon: Polygon): number {
})[0][1];
}

export function getMinXCoordinate(polygon: Polygon): number {
export function getMinXCoordinate(polygon: Array<Point>): number {
return polygon.sort((point1, point2) => {
if (point1[0] < point2[0]) {
return -1;
Expand All @@ -116,7 +115,7 @@ export function getMinXCoordinate(polygon: Polygon): number {
})[0][0];
}

export function compareOnY(polygon1: Polygon, polygon2: Polygon): number {
export function compareOnY(polygon1: Array<Point>, polygon2: Array<Point>): number {
const sort: number =
getMinYCoordinate(polygon1) - getMinYCoordinate(polygon2);
if (sort === 0) {
Expand All @@ -125,7 +124,7 @@ export function compareOnY(polygon1: Polygon, polygon2: Polygon): number {
return sort < 0 ? -1 : 1;
}

export function compareOnX(polygon1: Polygon, polygon2: Polygon): number {
export function compareOnX(polygon1: Array<Point>, polygon2: Array<Point>): number {
const sort: number =
getMinXCoordinate(polygon1) - getMinXCoordinate(polygon2);
if (sort === 0) {
Expand Down
102 changes: 55 additions & 47 deletions tests/geometry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,63 @@ import { expect } from "chai";

describe("Geometry functions", () => {
// 90° rectangle, overlaps polygonB
function polygonA(): geometry.Polygon {
return new geometry.Polygon(
[0.123, 0.53],
[0.175, 0.53],
[0.175, 0.546],
[0.123, 0.546],
);
}
const polygonA = new geometry.Polygon(
[0.123, 0.53],
[0.175, 0.53],
[0.175, 0.546],
[0.123, 0.546],
);

// 90° rectangle, overlaps polygonA
function polygonB(): geometry.Polygon {
return new geometry.Polygon(
[0.124, 0.535],
[0.19, 0.535],
[0.19, 0.546],
[0.124, 0.546],
);
}
const polygonB = new geometry.Polygon(
[0.124, 0.535],
[0.19, 0.535],
[0.19, 0.546],
[0.124, 0.546],
);

// not 90° rectangle, doesn't overlap any polygons
function polygonC(): geometry.Polygon {
return new geometry.Polygon(
[0.205, 0.407],
[0.379, 0.407],
[0.381, 0.43],
[0.207, 0.43],
);
}
const polygonC = new geometry.Polygon(
[0.205, 0.407],
[0.379, 0.407],
[0.381, 0.43],
[0.207, 0.43],
);

it("should get a polygon's bbox", () => {
const bboxA = geometry.getBbox(polygonA());
const bboxA = geometry.getBbox(polygonA);
expect(bboxA.xMin).to.be.eq(0.123);
expect(bboxA.yMin).to.be.eq(0.53);
expect(bboxA.xMax).to.be.eq(0.175);
expect(bboxA.yMax).to.be.eq(0.546);

const bboxB = geometry.getBbox(polygonB());
const bboxB = geometry.getBbox(polygonB);
expect(bboxB.xMin).to.be.eq(0.124);
expect(bboxB.yMin).to.be.eq(0.535);
expect(bboxB.xMax).to.be.eq(0.19);
expect(bboxB.yMax).to.be.eq(0.546);

const bboxC = geometry.getBbox(polygonC());
const bboxC = geometry.getBbox(polygonC);
expect(bboxC.xMin).to.be.eq(0.205);
expect(bboxC.yMin).to.be.eq(0.407);
expect(bboxC.xMax).to.be.eq(0.381);
expect(bboxC.yMax).to.be.eq(0.43);
});

it("should get a polygon's bounding box", () => {
expect(geometry.getBoundingBox(polygonA())).to.deep.ordered.members([
expect(geometry.getBoundingBox(polygonA)).to.deep.ordered.members([
[0.123, 0.53],
[0.175, 0.53],
[0.175, 0.546],
[0.123, 0.546],
]);
expect(geometry.getBoundingBox(polygonB())).to.deep.ordered.members([
expect(geometry.getBoundingBox(polygonB)).to.deep.ordered.members([
[0.124, 0.535],
[0.19, 0.535],
[0.19, 0.546],
[0.124, 0.546],
]);
expect(geometry.getBoundingBox(polygonC())).to.deep.ordered.members([
expect(geometry.getBoundingBox(polygonC)).to.deep.ordered.members([
[0.205, 0.407],
[0.381, 0.407],
[0.381, 0.43],
Expand All @@ -74,9 +68,11 @@ describe("Geometry functions", () => {
});

it("should calculate a polygon's centroid", () => {
expect(geometry.getCentroid(polygonA())).to.have.ordered.members([
0.149, 0.538,
]);
const utilsCentroid = geometry.getCentroid(polygonA);
const polygonCentroid = polygonA.getCentroid();
const expectedCentroid = [0.149, 0.538];
expect(utilsCentroid).to.deep.ordered.members(expectedCentroid);
expect(polygonCentroid).to.deep.ordered.members(expectedCentroid);
});

it("should determine if two polygons are on the same line", () => {
Expand All @@ -85,13 +81,19 @@ describe("Geometry functions", () => {
// Should only be in polygon C
const pointB: geometry.Point = [0.3, 0.42];

expect(geometry.isPointInPolygonY(pointA, polygonA())).to.be.true;
expect(geometry.isPointInPolygonY(pointA, polygonB())).to.be.true;
expect(geometry.isPointInPolygonY(pointA, polygonC())).to.be.false;

expect(geometry.isPointInPolygonY(pointB, polygonA())).to.be.false;
expect(geometry.isPointInPolygonY(pointB, polygonB())).to.be.false;
expect(geometry.isPointInPolygonY(pointB, polygonC())).to.be.true;
expect(geometry.isPointInPolygonY(pointA, polygonA)).to.be.true;
expect(polygonA.isPointInY(pointA)).to.be.true;
expect(geometry.isPointInPolygonY(pointA, polygonB)).to.be.true;
expect(polygonB.isPointInY(pointA)).to.be.true;
expect(geometry.isPointInPolygonY(pointA, polygonC)).to.be.false;
expect(polygonC.isPointInY(pointA)).to.be.false;

expect(geometry.isPointInPolygonY(pointB, polygonA)).to.be.false;
expect(polygonA.isPointInY(pointB)).to.be.false;
expect(geometry.isPointInPolygonY(pointB, polygonB)).to.be.false;
expect(polygonB.isPointInY(pointB)).to.be.false;
expect(geometry.isPointInPolygonY(pointB, polygonC)).to.be.true;
expect(polygonC.isPointInY(pointB)).to.be.true;
});

it("should determine if two polygons are on the same column", () => {
Expand All @@ -100,13 +102,19 @@ describe("Geometry functions", () => {
// Should only be in polygon C
const pointB: geometry.Point = [0.3, 0.42];

expect(geometry.isPointInPolygonX(pointA, polygonA())).to.be.true;
expect(geometry.isPointInPolygonX(pointA, polygonB())).to.be.true;
expect(geometry.isPointInPolygonX(pointA, polygonC())).to.be.false;

expect(geometry.isPointInPolygonX(pointB, polygonA())).to.be.false;
expect(geometry.isPointInPolygonX(pointB, polygonB())).to.be.false;
expect(geometry.isPointInPolygonX(pointB, polygonC())).to.be.true;
expect(geometry.isPointInPolygonX(pointA, polygonA)).to.be.true;
expect(polygonA.isPointInX(pointA)).to.be.true;
expect(geometry.isPointInPolygonX(pointA, polygonB)).to.be.true;
expect(polygonB.isPointInX(pointA)).to.be.true;
expect(geometry.isPointInPolygonX(pointA, polygonC)).to.be.false;
expect(polygonC.isPointInX(pointA)).to.be.false;

expect(geometry.isPointInPolygonX(pointB, polygonA)).to.be.false;
expect(polygonA.isPointInX(pointB)).to.be.false;
expect(geometry.isPointInPolygonX(pointB, polygonB)).to.be.false;
expect(polygonB.isPointInX(pointB)).to.be.false;
expect(geometry.isPointInPolygonX(pointB, polygonC)).to.be.true;
expect(polygonC.isPointInX(pointB)).to.be.true;
});

it("should merge two Bbox", () => {
Expand Down
Loading