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

refactor: kinks #15

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
7 changes: 6 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@
},
"overrides": [
{
"include": ["src/kinks.ts", "src/index.ts"],
"include": [
"src/kinks.ts",
"src/index.ts",
"src/utils.ts",
"src/ArcCollection.ts"
],
"linter": {
"enabled": true,
"rules": {
Expand Down
267 changes: 267 additions & 0 deletions src/ArcCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import type { Position } from "@turf/turf";
import { type ArcIter, ArcIter$new } from "./ArcIter";
import {
type Bounds,
Bounds$clone,
Bounds$height,
Bounds$mergeBounds,
Bounds$new,
} from "./bounds";
import {
type Segment,
calcArcBounds,
dedupIntersections,
intersectSegments,
} from "./utils";

export type ArcCollection = {
xx: Float64Array;
yy: Float64Array;
ii: Uint32Array;
nn: Uint32Array;
bb: Float64Array;
zz: any[] | null;
_zlimit: number;
_allBounds: Bounds | null;
_arcIter: ArcIter | null;
buf?: ArrayBuffer | null;
};

export function ArcCollection$new(coords: Position[][]): ArcCollection {
const xx: number[] = [];
const yy: number[] = [];
const nn = coords.map((points) => {
const n = points ? points.length : 0;
for (let i = 0; i < n; i++) {
xx.push(points[i][0]);
yy.push(points[i][1]);
}
return n;
});

const size = nn.length;
const self$xx = new Float64Array(xx);
const self$yy = new Float64Array(yy);
const self$nn = new Uint32Array(nn);
const self$zz = null;
const self$_zlimit = 0;
// generate array of starting idxs of each arc
const self$ii = new Uint32Array(size);
let idx = 0;
for (let j = 0; j < size; j++) {
self$ii[j] = idx;
idx += nn[j];
}
if (idx !== self$xx.length || self$xx.length !== self$yy.length) {
throw new Error("ArcCollection#initXYData() Counting error");
}
const data = ArcCollection$calcArcBounds_(self$xx, self$yy, self$nn);

const self: ArcCollection = {
xx: self$xx,
yy: self$yy,
ii: self$ii,
nn: self$nn,
zz: self$zz,
bb: data.bb,
_zlimit: self$_zlimit,
// Pre-allocate some path iterators for repeated use.
_arcIter: ArcIter$new(self$xx, self$yy),
_allBounds: data.bounds,
};

return self;
}

export function ArcCollection$calcArcBounds_(
xx: Float64Array,
yy: Float64Array,
nn: Uint32Array,
) {
const numArcs = nn.length;
const bb = new Float64Array(numArcs * 4);
const bounds = Bounds$new();
let arcOffs = 0;
let arcLen: number;
for (let i = 0; i < numArcs; i++) {
arcLen = nn[i];
if (arcLen > 0) {
let j = i * 4;
const b = calcArcBounds(xx, yy, arcOffs, arcLen) as number[];
bb[j++] = b[0];
bb[j++] = b[1];
bb[j++] = b[2];
bb[j] = b[3];
arcOffs += arcLen;
Bounds$mergeBounds(bounds, b);
}
}
return {
bb,
bounds,
};
}

export function ArcCollection$getBounds(self: ArcCollection) {
return Bounds$clone(self._allBounds!);
}

export function ArcCollection$forEachSegment(self: ArcCollection, cb: any) {
let count = 0;
for (let i = 0, n = ArcCollection$size(self); i < n; i++) {
count += ArcCollection$forEachArcSegment(self, i, cb);
}
return count;
}

export function ArcCollection$size(self: ArcCollection) {
return self.ii.length || 0;
}

export function ArcCollection$forEachArcSegment(
self: ArcCollection,
arcId: any,
cb: any,
): any {
const fw = arcId >= 0;
const absId = fw ? arcId : ~arcId;
const zlim = ArcCollection$getRetainedInterval(self);
const n = self.nn[absId];
const step = fw ? 1 : -1;
let v1 = fw ? self.ii[absId] : self.ii[absId] + n - 1;
let v2 = v1;
let count = 0;
for (let j = 1; j < n; j++) {
v2 += step;
if (zlim === 0 || (Array.isArray(self.zz) && self.zz[v2] >= zlim)) {
cb(v1, v2, self.xx, self.yy);
v1 = v2;
count++;
}
}
return count;
}

export function ArcCollection$getRetainedInterval(self: ArcCollection) {
return self._zlimit;
}

export function ArcCollection$getVertexData(self: ArcCollection) {
return {
xx: self.xx,
yy: self.yy,
zz: self.zz,
bb: self.bb,
nn: self.nn,
ii: self.ii,
};
}

export function ArcCollection$getUint32Array(
self: ArcCollection,
count: number,
) {
const bytes = count * 4;
if (!self.buf || self.buf.byteLength < bytes) {
self.buf = new ArrayBuffer(bytes);
}
return new Uint32Array(self.buf, 0, count);
}

export function ArcCollection$getAvgSegment2(self: ArcCollection) {
let dx = 0;
let dy = 0;
const count = ArcCollection$forEachSegment(
self,
(i: number, j: number, xx: number[], yy: number[]) => {
dx += Math.abs(xx[i] - xx[j]);
dy += Math.abs(yy[i] - yy[j]);
},
);
return [dx / count || 0, dy / count || 0];
}

export function ArcCollection$calcSegmentIntersectionStripeCount(
self: ArcCollection,
) {
const yrange = Bounds$height(ArcCollection$getBounds(self));
const segLen = ArcCollection$getAvgSegment2(self)[1];
let count = 1;
if (segLen > 0 && yrange > 0) {
count = Math.ceil(yrange / segLen / 20);
}
return count || 1;
}

export function ArcCollection$findSegmentIntersections(
self: ArcCollection,
): Segment[] {
const bounds = ArcCollection$getBounds(self);
const ymin = bounds.ymin;
const yrange = bounds.ymax - ymin;
const stripeCount = ArcCollection$calcSegmentIntersectionStripeCount(self);
const stripeSizes = new Uint32Array(stripeCount);
const stripeId =
stripeCount > 1
? (y: number) => Math.floor(((stripeCount - 1) * (y - ymin)) / yrange)
: () => 0;

// Count segments in each stripe
ArcCollection$forEachSegment(
self,
(id1: number, id2: number, _xx: number[], yy: number[]) => {
let s1 = stripeId(yy[id1]);
const s2 = stripeId(yy[id2]);
while (true) {
stripeSizes[s1] = stripeSizes[s1] + 2;
if (s1 === s2) break;
s1 += s2 > s1 ? 1 : -1;
}
},
);
// Allocate arrays for segments in each stripe
const stripeData = ArcCollection$getUint32Array(
self,
stripeSizes.reduce((acc, cur) => acc + cur, 0),
);
let offset = 0;

const stripes: Uint32Array[] = [];
for (let i = 0; i < stripeSizes.length; i++) {
const stripeSize = stripeSizes[i];
const start = offset;
offset += stripeSize;
stripes.push(stripeData.subarray(start, offset));
}

// Assign segment ids to each stripe
stripeSizes.fill(0);
ArcCollection$forEachSegment(
self,
(id1: number, id2: number, _xx: number[], yy: number[]) => {
let s1 = stripeId(yy[id1]);
const s2 = stripeId(yy[id2]);

while (true) {
const count = stripeSizes[s1];
stripeSizes[s1] = count + 2;
const stripe = stripes[s1];
stripe[count] = id1;
stripe[count + 1] = id2;
if (s1 === s2) break;
s1 += s2 > s1 ? 1 : -1;
}
},
);
// Detect intersections among segments in each stripe.
const raw = ArcCollection$getVertexData(self);
const intersections: Segment[] = [];

for (let i = 0; i < stripeCount; i++) {
const arr = intersectSegments(stripes[i], raw.xx, raw.yy);
for (let j = 0; j < arr.length; j++) {
intersections.push(arr[j]);
}
}
return dedupIntersections(intersections);
}
24 changes: 24 additions & 0 deletions src/ArcIter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export type ArcIter = {
_i: number;
_n: number;
_inc: number;
_xx: Float64Array;
_yy: Float64Array;
i: number;
x: number;
y: number;
};

export function ArcIter$new(xx: Float64Array, yy: Float64Array): ArcIter {
const self: ArcIter = {
_i: 0,
_n: 0,
_inc: 1,
_xx: xx,
_yy: yy,
i: 0,
x: 0,
y: 0,
};
return self;
}
Loading