Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linzjs-geojson): add Area.ring() method and Area tests #2891

Merged
merged 2 commits into from
Aug 15, 2023
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
95 changes: 95 additions & 0 deletions packages/linzjs-geojson/src/multipolygon/__tests__/area.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import o from 'ospec';
import { MultiPolygon, Polygon, Ring } from '../../types.js';
import { Area } from '../area.js';

o.spec('Area', () => {
o('Ring', () => {
const ring: Ring = [
[10, 10],
[20, 10],
[20, 20],
[10, 20],
[10, 10],
];
const area = Area.ring(ring);
o(area).equals(100);
});
o('Polygon, single exterior ring', () => {
const poly: Polygon = [
[
[10, 10],
[20, 10],
[20, 20],
[10, 20],
[10, 10],
],
];
const area = Area.polygon(poly);
o(area).equals(100);
});
o('Polygon, interior rings', () => {
const poly: Polygon = [
[
[10, 10],
[20, 10],
[20, 20],
[10, 20],
[10, 10],
],
[
[12, 14],
[12, 18],
[16, 18],
[16, 14],
[12, 14],
],
[
[16, 12],
[16, 14],
[18, 14],
[18, 12],
[16, 12],
],
];
const area = Area.polygon(poly);
o(area).equals(80);
});
o('MultiPolygon', () => {
const multipoly: MultiPolygon = [
[
[
[10, 10],
[20, 10],
[20, 20],
[10, 20],
[10, 10],
],
[
[12, 14],
[12, 18],
[16, 18],
[16, 14],
[12, 14],
],
[
[16, 12],
[16, 14],
[18, 14],
[18, 12],
[16, 12],
],
],
[
[
[20, 10],
[30, 10],
[30, 20],
[20, 20],
[20, 10],
],
],
];
const area = Area.multiPolygon(multipoly);
o(area).equals(180);
});
});
54 changes: 41 additions & 13 deletions packages/linzjs-geojson/src/multipolygon/area.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,49 @@
import { MultiPolygon, Polygon } from '../types.js';
import { MultiPolygon, Polygon, Ring } from '../types.js';

export const Area = {
multiPolygon(mp: MultiPolygon): number {
/**
* Calculate the cartesian area of a ring using the shoelace formula.
* Assumes the ring is well-formed and simple (i.e. not self-intersecting).
* @param ring The ring to calculate the area of.
* @returns The area of the ring.
*/
ring(ring: Ring): number {
let total = 0;
for (const p of mp) total += Area.polygon(p);
// if (clockwise) ring.reverse();
for (let i = 0; i < ring.length - 1; i++) total += ring[i][0] * ring[i + 1][1] - ring[i][1] * ring[i + 1][0];
return total / 2;
},
/**
* Calculate the cartesian area of a polygon using the shoelace formula.
* Assumes the polygon is well-formed and simple (i.e. not self-intersecting).
*
* [GeoJSON Polygons][1] are an array of rings, where the first ring is the
* exterior, and any subsequent rings are interior rings (holes).
* The coordinates of the exterior ring are ordered counterclockwise,
* while the coordinates of interior rings are ordered clockwise.
* Area.ring() gives a negative area value for these clockwise interior rings,
* hence adding the area of all rings together gives the valid area of the
* polygon, as adding the negative area of the interior rings subtracts it
* from the positive area of the exterior ring.
*
* [1]: https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6
* @param poly The polygon to calculate the area of.
* @returns The area of the polygon.
*/
polygon(poly: Polygon): number {
let total = 0;
for (const ring of poly) total += Area.ring(ring);
return total;
},
polygon(p: Polygon): number {
const ring = p[0];
/**
* Calculate the cartesian area of a multipolygon using the shoelace formula.
* Assumes the multipolygon is well-formed and simple (i.e. not self-intersecting).
* @param multipoly The polygon to calculate the area of.
* @returns The area of the polygon.
*/
multiPolygon(multipoly: MultiPolygon): number {
let total = 0;
for (let i = 0; i < ring.length - 1; i++) total += ring[i][0] * ring[i + 1][1] - ring[i][1] * ring[i + 1][0];

for (let poly = 1; poly < p.length; poly++) {
const ring = p[poly];
for (let i = 0; i < ring.length - 1; i++) total -= ring[i][0] * ring[i + 1][1] - ring[i][1] * ring[i + 1][0];
}

return total / 2;
for (const poly of multipoly) total += Area.polygon(poly);
return total;
},
};
Loading