Skip to content

Commit

Permalink
Merge pull request #8 from edumeet/feat-static-getdistance
Browse files Browse the repository at this point in the history
Feat static getdistance
  • Loading branch information
pnts-se authored Mar 28, 2023
2 parents ffbf448 + 8a196cb commit 8aecc7f
Show file tree
Hide file tree
Showing 12 changed files with 3,312 additions and 141 deletions.
145 changes: 145 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"extends": ["plugin:@typescript-eslint/recommended"],
"env": { "node": true },
"rules": {
"array-bracket-spacing": [ 2, "always", {
"objectsInArrays": true,
"arraysInArrays": true
}],
"arrow-parens": [ 2, "always" ],
"arrow-spacing": 2,
"block-spacing": [ 2, "always" ],
"brace-style": [ 2, "1tbs", { "allowSingleLine": true } ],
"camelcase": 2,
"comma-spacing": [ 2, { "before": false, "after": true } ],
"comma-style": 2,
"computed-property-spacing": 2,
"constructor-super": 2,
"func-call-spacing": 2,
"generator-star-spacing": 2,
"guard-for-in": 2,
"indent": [ 2, "tab", { "SwitchCase": 1 } ],
"key-spacing": [ 2, {
"singleLine": {
"beforeColon": false,
"afterColon": true
},
"multiLine": {
"beforeColon": false,
"afterColon": true
}
}],
"keyword-spacing": 2,
"linebreak-style": [ 2, "unix" ],
"lines-around-comment": [ 2, {
"allowBlockStart": true,
"allowObjectStart": true,
"beforeBlockComment": true,
"beforeLineComment": false
}],
"max-len": [ 2, 90, {
"tabWidth": 2,
"comments": 90,
"ignoreUrls": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true,
"ignoreRegExpLiterals": true
}],
"newline-after-var": 2,
"newline-before-return": 2,
"newline-per-chained-call": 2,
"no-alert": 2,
"no-caller": 2,
"no-case-declarations": 2,
"no-catch-shadow": 2,
"no-class-assign": 2,
"no-confusing-arrow": 2,
"no-console": 2,
"no-const-assign": 2,
"no-debugger": 2,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-div-regex": 2,
"no-empty": [ 2, { "allowEmptyCatch": true } ],
"no-empty-pattern": 2,
"no-else-return": 0,
"no-eval": 2,
"no-extend-native": 2,
"no-ex-assign": 2,
"no-extra-bind": 2,
"no-extra-boolean-cast": 2,
"no-extra-label": 2,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-func-assign": 2,
"no-global-assign": 2,
"no-implicit-coercion": 2,
"no-implicit-globals": 2,
"no-inner-declarations": 2,
"no-invalid-regexp": 2,
"no-invalid-this": 2,
"no-irregular-whitespace": 2,
"no-lonely-if": 2,
"no-mixed-operators": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-multiple-empty-lines": [ 1, { "max": 1, "maxEOF": 0, "maxBOF": 0 } ],
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-proto": 2,
"no-regex-spaces": 2,
"no-restricted-imports": 2,
"no-return-assign": 2,
"no-self-assign": 2,
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-sparse-arrays": 2,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-unexpected-multiline": 2,
"no-unmodified-loop-condition": 2,
"no-unreachable": 2,
"no-unused-vars": [ 1, { "vars": "all", "args": "after-used" }],
"no-useless-call": 2,
"no-useless-computed-key": 2,
"no-useless-concat": 2,
"no-useless-rename": 2,
"no-var": 2,
"no-whitespace-before-property": 2,
"object-curly-newline": 0,
"object-curly-spacing": [ 2, "always" ],
"object-property-newline": [ 2, { "allowMultiplePropertiesPerLine": true } ],
"prefer-const": 2,
"prefer-rest-params": 2,
"prefer-spread": 2,
"prefer-template": 2,
"quotes": [ 2, "single", { "avoidEscape": true } ],
"semi": [ 2, "always" ],
"semi-spacing": 2,
"space-before-blocks": 2,
"space-before-function-paren": [ 2, {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}],
"space-in-parens": [ 2, "never" ],
"spaced-comment": [ 2, "always" ],
"strict": 2,
"valid-typeof": 2,
"yoda": 2
}
}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/node_modules
/node_modules
.vscode
yarn-error.log
9 changes: 9 additions & 0 deletions __tests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"module": "commonjs",
"target": "es6",
"declaration": true,
},
"include": [ "**/*" ]
}
100 changes: 100 additions & 0 deletions __tests__/unit-tests/KDTree.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { KDTree, KDPoint } from '../../src/KDTree';

let geoPoints: KDPoint[];
let additionalGeoPoints: KDPoint[];

beforeEach(() => {
geoPoints = [
new KDPoint([ 59.9139, 10.7522 ], { name: 'Oslo', load: 0 }),
new KDPoint([ 63.4305, 10.3951 ], { name: 'Trondheim', load: 0 }),
new KDPoint([ 58.9690, 5.7331 ], { name: 'Stavanger', load: 0 }),
new KDPoint([ 60.3913, 5.3221 ], { name: 'Bergen', load: 0 }),
new KDPoint([ 69.6496, 18.9553 ], { name: 'Tromsø', load: 0 }),
];
additionalGeoPoints = [
new KDPoint([ 60.4675, 7.0719 ], { name: 'Eidfjord', load: 0 }),
new KDPoint([ 62.4722, 6.1497 ], { name: 'Ålesund', load: 0 }),
new KDPoint([ 66.3167, 14.1667 ], { name: 'Mo i Rana', load: 0 }),
new KDPoint([ 67.2833, 14.4000 ], { name: 'Bodø', load: 0 }),
new KDPoint([ 63.1111, 7.7417 ], { name: 'Kristiansund', load: 0 }),
];
});

test('Constructor should work', () => {
expect(() => new KDTree(geoPoints)).not.toThrow();
});

test('Should be able to add new node to tree', () => {
const sut = new KDTree([]);
const point = new KDPoint([ 59.65059, 6.35415 ],
{ name: 'Sauda', load: 0 });

expect(sut.root).toBe(undefined);
sut.addNode(point);

expect(sut.root.kdPoint).toBe(point);
});

test('Rebalance should work', () => {
const sut = new KDTree(geoPoints);
let score = sut.balanceScore();

expect(score).toBe(1.5);

additionalGeoPoints.forEach((point) => {
sut.addNode(point);
});

score = sut.balanceScore();
expect(score).toBeGreaterThan(1.6);

sut.rebalance();

score = sut.balanceScore();
expect(score).toBeLessThan(1.34);
});

test('Should return closest point', () => {
const target = new KDPoint(
[ 60.5166646, 8.1999992 ],
{ name: 'Geilo' }
);

const sut = new KDTree(geoPoints);
const nearest = sut.nearestNeighbors(
target, 1, (point) => point.appData.load as number <= 0.8);

expect(nearest[0][0].appData).toBe(geoPoints[1].appData);
});

test('Should diqualify on filter', () => {
const target = new KDPoint(
[ 60.5166646, 8.1999992 ],
{ name: 'Geilo' }
);

const sut = new KDTree(geoPoints);

sut.root.left.kdPoint.appData.load = 0.9;
const nearest = sut.nearestNeighbors(
target, 1, (point) => point.appData.load as number <= 0.8);

expect(nearest[0][0].appData).toBe(geoPoints[2].appData);
});

test('Should return distance', () => {
const target = new KDPoint(
[ 60.5166646, 8.1999992 ],
{ name: 'Geilo' }
);

let distance = KDTree.getDistance(target, geoPoints[0]);

expect(distance).toBeGreaterThan(156.0);
expect(distance).toBeLessThan(156.1);

distance = KDTree.getDistance(target, geoPoints[1]);

expect(distance).toBeGreaterThan(343.6);
expect(distance).toBeLessThan(343.7);
});
9 changes: 9 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
modulePathIgnorePatterns: [ '<rootDir>/lib' ],
transform: {
'^.+\\.[t]s$': [
'ts-jest', { tsconfig: 'src/tsconfig.json' }
]
}
};
22 changes: 16 additions & 6 deletions lib/KDTree.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* A point in a k-dimensional space. The position array is the coordinates of the point in the space.
* A point in a k-dimensional space.
* The position array is the coordinates of the point in the space.
*/
export declare class KDPoint {
position: number[];
Expand Down Expand Up @@ -52,19 +53,27 @@ declare class KDNode {
* const geoTree = new KDTree(geoPoints);
*
* // Add a new point to the tree
* geoTree.addNode(new KDPoint([ 59.65059, 6.35415 ], { name: 'Sauda', load: 0 })); // Sauda, Norway
* geoTree.addNode(
new KDPoint([ 59.65059, 6.35415 ],
{ name: 'Sauda', load: 0 })
); // Sauda, Norway
*
* // 1 is a perfect balance. For a large tree, 1.5 is an unbalanced tree.
* const score = geoTree.balanceScore());
* const score = geoTree.balanceScore();
*
* // Rebalance the tree
* geoTree.rebalance();
*
* // Find the nearest neighbor for a target point
* const target = new KDPoint([ 60.5166646, 8.1999992 ], { name: 'Geilo' }); // Geilo, Norway
* const target = new KDPoint(
[ 60.5166646, 8.1999992 ],
{ name: 'Geilo' }
); // Geilo, Norway
*
* // Find the 3 nearest neighbors, but points with a load of more than 0.1 will be ignored
* const nearest = geoTree.nearestNeighbors(target, 3, (point) => point.appData.load as number <= 0.1);
* // Find the 3 nearest neighbors,
but points with a load of more than 0.1 will be ignored
* const nearest = geoTree.nearestNeighbors(
target, 3, (point) => point.appData.load as number <= 0.1);
*/
export declare class KDTree {
private k;
Expand All @@ -84,5 +93,6 @@ export declare class KDTree {
private countNodes;
private treeHeight;
nearestNeighbors(target: KDPoint, n: number, filter?: (point: KDPoint) => boolean): [KDPoint, number][] | undefined;
static getDistance(p1: KDPoint, p2: KDPoint): number;
}
export {};
Loading

0 comments on commit 8aecc7f

Please sign in to comment.