core/aggregators/representation.grid.aggregator.ts
core/aggregators/representation.grid.aggregator.ts is a file in GovLab Patterns. 55 lines of code and 19 definitions.
import type { GridSummary } from "#types/representation.types";
import type { Rng } from "#types/seed.types";
import { foldRecords } from "#core/selectors/record.selector";
import { increment } from "#core/counters/base.counter";
import { weightedChoice } from "#core/selectors/counter.selector";
const PAIR = 2;
const DEFAULT_CELL = 1;
const CELL_SEP = ",";
const isNumberPair = function isNumberPair(value: unknown): value is [number, number] {
return (
Array.isArray(value) && value.length === PAIR && typeof value[0] === "number" && typeof value[1] === "number"
);
};
export class GridAccumulator {
private readonly field: string;
private readonly cellSize: number;
private readonly cells = new Map<string, number>();
private points = 0;
public constructor(field: string, cellSize = DEFAULT_CELL) {
this.field = field;
this.cellSize = cellSize;
}
public update(chunk: readonly unknown[]): void {
foldRecords(chunk, this.field, (value) => {
this.observe(value);
});
}
public sample(rng: Rng): [number, number] | null {
const key = weightedChoice(rng, this.cells);
if (key === null) {
return null;
}
const [cellX = 0, cellY = 0] = key.split(CELL_SEP).map(Number);
return [(cellX + rng.next()) * this.cellSize, (cellY + rng.next()) * this.cellSize];
}
public result(): GridSummary {
let densestCell = "";
let densestCount = this.cells.size === 0 ? 0 : -1;
for (const [cell, count] of this.cells) {
if (count > densestCount) {
densestCell = cell;
densestCount = count;
}
}
return { densestCell, densestCount, distinctCells: this.cells.size, field: this.field, points: this.points };
}
private observe(value: unknown): void {
if (!isNumberPair(value)) {
return;
}
this.points += 1;
const [x, y] = value;
increment(this.cells, `${Math.floor(x / this.cellSize)}${CELL_SEP}${Math.floor(y / this.cellSize)}`);
}
}