all these changes

This commit is contained in:
Jake Kasper
2026-04-09 13:19:47 -05:00
parent e83a51a051
commit 65315f36d1
39102 changed files with 7932979 additions and 567 deletions

20
frontend/node_modules/geojson-rbush/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2018 Denis Carriere
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

213
frontend/node_modules/geojson-rbush/README.md generated vendored Normal file
View File

@@ -0,0 +1,213 @@
# GeoJSON RBush
[![Build Status](https://travis-ci.org/DenisCarriere/geojson-rbush.svg?branch=master)](https://travis-ci.org/DenisCarriere/geojson-rbush)
[![npm version](https://badge.fury.io/js/geojson-rbush.svg)](https://badge.fury.io/js/geojson-rbush)
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/DenisCarriere/geojson-rbush/master/LICENSE)
GeoJSON implementation of [RBush](https://github.com/mourner/rbush) — a high-performance JavaScript R-tree-based 2D spatial index for points and rectangles.
## Install
**npm**
```bash
$ npm install --save geojson-rbush
```
## API
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
#### Table of Contents
- [rbush](#rbush)
- [insert](#insert)
- [load](#load)
- [remove](#remove)
- [clear](#clear)
- [search](#search)
- [collides](#collides)
- [all](#all)
- [toJSON](#tojson)
- [fromJSON](#fromjson)
### rbush
GeoJSON implementation of [RBush](https://github.com/mourner/rbush#rbush) spatial index.
**Parameters**
- `maxEntries` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** defines the maximum number of entries in a tree node. 9 (used by default) is a
reasonable choice for most applications. Higher value means faster insertion and slower search, and vice versa. (optional, default `9`)
**Examples**
```javascript
var geojsonRbush = require('geojson-rbush').default;
var tree = geojsonRbush();
```
Returns **RBush** GeoJSON RBush
### insert
[insert](https://github.com/mourner/rbush#data-format)
**Parameters**
- `feature` **Feature** insert single GeoJSON Feature
**Examples**
```javascript
var poly = turf.polygon([[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]);
tree.insert(poly)
```
Returns **RBush** GeoJSON RBush
### load
[load](https://github.com/mourner/rbush#bulk-inserting-data)
**Parameters**
- `features` **(FeatureCollection | [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;Feature>)** load entire GeoJSON FeatureCollection
**Examples**
```javascript
var polys = turf.polygons([
[[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]],
[[[-93, 32], [-83, 32], [-83, 39], [-93, 39], [-93, 32]]]
]);
tree.load(polys);
```
Returns **RBush** GeoJSON RBush
### remove
[remove](https://github.com/mourner/rbush#removing-data)
**Parameters**
- `feature` **Feature** remove single GeoJSON Feature
- `equals` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Pass a custom equals function to compare by value for removal.
**Examples**
```javascript
var poly = turf.polygon([[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]);
tree.remove(poly);
```
Returns **RBush** GeoJSON RBush
### clear
[clear](https://github.com/mourner/rbush#removing-data)
**Examples**
```javascript
tree.clear()
```
Returns **RBush** GeoJSON Rbush
### search
[search](https://github.com/mourner/rbush#search)
**Parameters**
- `geojson` **(BBox | FeatureCollection | Feature)** search with GeoJSON
**Examples**
```javascript
var poly = turf.polygon([[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]);
tree.search(poly);
```
Returns **FeatureCollection** all features that intersects with the given GeoJSON.
### collides
[collides](https://github.com/mourner/rbush#collisions)
**Parameters**
- `geojson` **(BBox | FeatureCollection | Feature)** collides with GeoJSON
**Examples**
```javascript
var poly = turf.polygon([[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]);
tree.collides(poly);
```
Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** true if there are any items intersecting the given GeoJSON, otherwise false.
### all
[all](https://github.com/mourner/rbush#search)
**Examples**
```javascript
tree.all()
```
Returns **FeatureCollection** all the features in RBush
### toJSON
[toJSON](https://github.com/mourner/rbush#export-and-import)
**Examples**
```javascript
var exported = tree.toJSON()
```
Returns **any** export data as JSON object
### fromJSON
[fromJSON](https://github.com/mourner/rbush#export-and-import)
**Parameters**
- `json` **any** import previously exported data
**Examples**
```javascript
var exported = {
"children": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [110, 50]
},
"properties": {},
"bbox": [110, 50, 110, 50]
}
],
"height": 1,
"leaf": true,
"minX": 110,
"minY": 50,
"maxX": 110,
"maxY": 50
}
tree.fromJSON(exported)
```
Returns **RBush** GeoJSON RBush

19
frontend/node_modules/geojson-rbush/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,19 @@
import { BBox, Feature, FeatureCollection, Geometry, GeoJsonProperties } from 'geojson'
declare class RBush<G extends Geometry, P extends GeoJsonProperties> {
insert(feature: Feature<G, P>): RBush<G, P>;
load(features: FeatureCollection<G, P> | Feature<G, P>[]): RBush<G, P>;
remove(feature: Feature<G, P>, equals?: (a: Feature<G, P>, b: Feature<G, P>) => boolean): RBush<G, P>;
clear(): RBush<G, P>;
search(geojson: Feature<G, P> | FeatureCollection<G, P> | BBox): FeatureCollection<G, P>;
all(): FeatureCollection<any>;
collides(geosjon: Feature<G, P> | FeatureCollection<G, P> | BBox): boolean;
toJSON(): any;
fromJSON(data: any): RBush<G, P>;
}
/**
* https://github.com/mourner/rbush
*/
export default function rbush<G extends Geometry = Geometry, P = GeoJsonProperties>(maxEntries?: number): RBush<G, P>;

208
frontend/node_modules/geojson-rbush/index.js generated vendored Normal file
View File

@@ -0,0 +1,208 @@
var rbush = require('rbush');
var helpers = require('@turf/helpers');
var meta = require('@turf/meta');
var turfBBox = require('@turf/bbox').default;
var featureEach = meta.featureEach;
var coordEach = meta.coordEach;
var polygon = helpers.polygon;
var featureCollection = helpers.featureCollection;
/**
* GeoJSON implementation of [RBush](https://github.com/mourner/rbush#rbush) spatial index.
*
* @name rbush
* @param {number} [maxEntries=9] defines the maximum number of entries in a tree node. 9 (used by default) is a
* reasonable choice for most applications. Higher value means faster insertion and slower search, and vice versa.
* @returns {RBush} GeoJSON RBush
* @example
* var geojsonRbush = require('geojson-rbush').default;
* var tree = geojsonRbush();
*/
function geojsonRbush(maxEntries) {
var tree = new rbush(maxEntries);
/**
* [insert](https://github.com/mourner/rbush#data-format)
*
* @param {Feature} feature insert single GeoJSON Feature
* @returns {RBush} GeoJSON RBush
* @example
* var poly = turf.polygon([[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]);
* tree.insert(poly)
*/
tree.insert = function (feature) {
if (feature.type !== 'Feature') throw new Error('invalid feature');
feature.bbox = feature.bbox ? feature.bbox : turfBBox(feature);
return rbush.prototype.insert.call(this, feature);
};
/**
* [load](https://github.com/mourner/rbush#bulk-inserting-data)
*
* @param {FeatureCollection|Array<Feature>} features load entire GeoJSON FeatureCollection
* @returns {RBush} GeoJSON RBush
* @example
* var polys = turf.polygons([
* [[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]],
* [[[-93, 32], [-83, 32], [-83, 39], [-93, 39], [-93, 32]]]
* ]);
* tree.load(polys);
*/
tree.load = function (features) {
var load = [];
// Load an Array of Features
if (Array.isArray(features)) {
features.forEach(function (feature) {
if (feature.type !== 'Feature') throw new Error('invalid features');
feature.bbox = feature.bbox ? feature.bbox : turfBBox(feature);
load.push(feature);
});
} else {
// Load a FeatureCollection
featureEach(features, function (feature) {
if (feature.type !== 'Feature') throw new Error('invalid features');
feature.bbox = feature.bbox ? feature.bbox : turfBBox(feature);
load.push(feature);
});
}
return rbush.prototype.load.call(this, load);
};
/**
* [remove](https://github.com/mourner/rbush#removing-data)
*
* @param {Feature} feature remove single GeoJSON Feature
* @param {Function} equals Pass a custom equals function to compare by value for removal.
* @returns {RBush} GeoJSON RBush
* @example
* var poly = turf.polygon([[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]);
*
* tree.remove(poly);
*/
tree.remove = function (feature, equals) {
if (feature.type !== 'Feature') throw new Error('invalid feature');
feature.bbox = feature.bbox ? feature.bbox : turfBBox(feature);
return rbush.prototype.remove.call(this, feature, equals);
};
/**
* [clear](https://github.com/mourner/rbush#removing-data)
*
* @returns {RBush} GeoJSON Rbush
* @example
* tree.clear()
*/
tree.clear = function () {
return rbush.prototype.clear.call(this);
};
/**
* [search](https://github.com/mourner/rbush#search)
*
* @param {BBox|FeatureCollection|Feature} geojson search with GeoJSON
* @returns {FeatureCollection} all features that intersects with the given GeoJSON.
* @example
* var poly = turf.polygon([[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]);
*
* tree.search(poly);
*/
tree.search = function (geojson) {
var features = rbush.prototype.search.call(this, this.toBBox(geojson));
return featureCollection(features);
};
/**
* [collides](https://github.com/mourner/rbush#collisions)
*
* @param {BBox|FeatureCollection|Feature} geojson collides with GeoJSON
* @returns {boolean} true if there are any items intersecting the given GeoJSON, otherwise false.
* @example
* var poly = turf.polygon([[[-78, 41], [-67, 41], [-67, 48], [-78, 48], [-78, 41]]]);
*
* tree.collides(poly);
*/
tree.collides = function (geojson) {
return rbush.prototype.collides.call(this, this.toBBox(geojson));
};
/**
* [all](https://github.com/mourner/rbush#search)
*
* @returns {FeatureCollection} all the features in RBush
* @example
* tree.all()
*/
tree.all = function () {
var features = rbush.prototype.all.call(this);
return featureCollection(features);
};
/**
* [toJSON](https://github.com/mourner/rbush#export-and-import)
*
* @returns {any} export data as JSON object
* @example
* var exported = tree.toJSON()
*/
tree.toJSON = function () {
return rbush.prototype.toJSON.call(this);
};
/**
* [fromJSON](https://github.com/mourner/rbush#export-and-import)
*
* @param {any} json import previously exported data
* @returns {RBush} GeoJSON RBush
* @example
* var exported = {
* "children": [
* {
* "type": "Feature",
* "geometry": {
* "type": "Point",
* "coordinates": [110, 50]
* },
* "properties": {},
* "bbox": [110, 50, 110, 50]
* }
* ],
* "height": 1,
* "leaf": true,
* "minX": 110,
* "minY": 50,
* "maxX": 110,
* "maxY": 50
* }
* tree.fromJSON(exported)
*/
tree.fromJSON = function (json) {
return rbush.prototype.fromJSON.call(this, json);
};
/**
* Converts GeoJSON to {minX, minY, maxX, maxY} schema
*
* @private
* @param {BBox|FeatureCollection|Feature} geojson feature(s) to retrieve BBox from
* @returns {Object} converted to {minX, minY, maxX, maxY}
*/
tree.toBBox = function (geojson) {
var bbox;
if (geojson.bbox) bbox = geojson.bbox;
else if (Array.isArray(geojson) && geojson.length === 4) bbox = geojson;
else if (Array.isArray(geojson) && geojson.length === 6) bbox = [geojson[0], geojson[1], geojson[3], geojson[4]];
else if (geojson.type === 'Feature') bbox = turfBBox(geojson);
else if (geojson.type === 'FeatureCollection') bbox = turfBBox(geojson);
else throw new Error('invalid geojson')
return {
minX: bbox[0],
minY: bbox[1],
maxX: bbox[2],
maxY: bbox[3]
};
};
return tree;
}
module.exports = geojsonRbush;
module.exports.default = geojsonRbush;

View File

@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2018, Vladimir Agafonkin
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View File

@@ -0,0 +1,28 @@
## quickselect [![Build Status](https://travis-ci.org/mourner/quickselect.svg?branch=master)](https://travis-ci.org/mourner/quickselect)
A tiny and fast [selection algorithm](https://en.wikipedia.org/wiki/Selection_algorithm) in JavaScript
(specifically, [Floyd-Rivest selection](https://en.wikipedia.org/wiki/Floyd%E2%80%93Rivest_algorithm)).
```js
quickselect(array, k[, left, right, compareFn]);
```
Rearranges items so that all items in the `[left, k]` are the smallest.
The `k`-th element will have the `(k - left + 1)`-th smallest value in `[left, right]`.
- `array`: the array to partially sort (in place)
- `k`: middle index for partial sorting (as defined above)
- `left`: left index of the range to sort (`0` by default)
- `right`: right index (last index of the array by default)
- `compareFn`: compare function
Example:
```js
var arr = [65, 28, 59, 33, 21, 56, 22, 95, 50, 12, 90, 53, 28, 77, 39];
quickselect(arr, 8);
// arr is [39, 28, 28, 33, 21, 12, 22, 50, 53, 56, 59, 65, 90, 77, 95]
// ^^ middle index
```

View File

@@ -0,0 +1,54 @@
export default function quickselect(arr, k, left, right, compare) {
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
}
function quickselectStep(arr, k, left, right, compare) {
while (right > left) {
if (right - left > 600) {
var n = right - left + 1;
var m = k - left + 1;
var z = Math.log(n);
var s = 0.5 * Math.exp(2 * z / 3);
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
quickselectStep(arr, k, newLeft, newRight, compare);
}
var t = arr[k];
var i = left;
var j = right;
swap(arr, left, k);
if (compare(arr[right], t) > 0) swap(arr, left, right);
while (i < j) {
swap(arr, i, j);
i++;
j--;
while (compare(arr[i], t) < 0) i++;
while (compare(arr[j], t) > 0) j--;
}
if (compare(arr[left], t) === 0) swap(arr, left, j);
else {
j++;
swap(arr, j, right);
}
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function defaultCompare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}

View File

@@ -0,0 +1,43 @@
{
"name": "quickselect",
"version": "2.0.0",
"description": "A tiny and fast selection algorithm in JavaScript.",
"module": "index.js",
"main": "quickselect.js",
"dependencies": {},
"devDependencies": {
"eslint": "^4.19.1",
"eslint-config-mourner": "^2.0.3",
"esm": "^3.0.15",
"rollup": "^0.57.1",
"tape": "^4.9.0"
},
"eslintConfig": {
"extends": "mourner",
"parserOptions": {
"sourceType": "module"
}
},
"scripts": {
"pretest": "eslint index.js test.js bench.js",
"test": "node -r esm test.js",
"bench": "node -r esm bench.js",
"build": "rollup -c",
"prepublishOnly": "npm run build"
},
"files": [
"index.js",
"quickselect.js"
],
"keywords": [
"selection",
"algorithm",
"quickselect",
"sort",
"partial",
"floyd",
"rivest"
],
"author": "Vladimir Agafonkin",
"license": "ISC"
}

View File

@@ -0,0 +1,63 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.quickselect = factory());
}(this, (function () { 'use strict';
function quickselect(arr, k, left, right, compare) {
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
}
function quickselectStep(arr, k, left, right, compare) {
while (right > left) {
if (right - left > 600) {
var n = right - left + 1;
var m = k - left + 1;
var z = Math.log(n);
var s = 0.5 * Math.exp(2 * z / 3);
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
quickselectStep(arr, k, newLeft, newRight, compare);
}
var t = arr[k];
var i = left;
var j = right;
swap(arr, left, k);
if (compare(arr[right], t) > 0) swap(arr, left, right);
while (i < j) {
swap(arr, i, j);
i++;
j--;
while (compare(arr[i], t) < 0) i++;
while (compare(arr[j], t) > 0) j--;
}
if (compare(arr[left], t) === 0) swap(arr, left, j);
else {
j++;
swap(arr, j, right);
}
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function defaultCompare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
return quickselect;
})));

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Vladimir Agafonkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,218 @@
RBush
=====
RBush is a high-performance JavaScript library for 2D **spatial indexing** of points and rectangles.
It's based on an optimized **R-tree** data structure with **bulk insertion** support.
*Spatial index* is a special data structure for points and rectangles
that allows you to perform queries like "all items within this bounding box" very efficiently
(e.g. hundreds of times faster than looping over all items).
It's most commonly used in maps and data visualizations.
[![Build Status](https://travis-ci.org/mourner/rbush.svg?branch=master)](https://travis-ci.org/mourner/rbush)
[![](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects)
## Demos
The demos contain visualization of trees generated from 50k bulk-loaded random points.
Open web console to see benchmarks;
click on buttons to insert or remove items;
click to perform search under the cursor.
* [randomly clustered data](http://mourner.github.io/rbush/viz/viz-cluster.html)
* [uniformly distributed random data](http://mourner.github.io/rbush/viz/viz-uniform.html)
## Install
Install with NPM (`npm install rbush`), or use CDN links for browsers:
[rbush.js](https://unpkg.com/rbush@2.0.1/rbush.js),
[rbush.min.js](https://unpkg.com/rbush@2.0.1/rbush.min.js)
## Usage
### Creating a Tree
```js
const tree = new RBush();
```
An optional argument to `RBush` defines the maximum number of entries in a tree node.
`9` (used by default) is a reasonable choice for most applications.
Higher value means faster insertion and slower search, and vice versa.
```js
const tree = new RBush(16);
```
### Adding Data
Insert an item:
```js
const item = {
minX: 20,
minY: 40,
maxX: 30,
maxY: 50,
foo: 'bar'
};
tree.insert(item);
```
### Removing Data
Remove a previously inserted item:
```js
tree.remove(item);
```
By default, RBush removes objects by reference.
However, you can pass a custom `equals` function to compare by value for removal,
which is useful when you only have a copy of the object you need removed (e.g. loaded from server):
```js
tree.remove(itemCopy, (a, b) => {
return a.id === b.id;
});
```
Remove all items:
```js
tree.clear();
```
### Data Format
By default, RBush assumes the format of data points to be an object
with `minX`, `minY`, `maxX` and `maxY` properties.
You can customize this by overriding `toBBox`, `compareMinX` and `compareMinY` methods like this:
```js
class MyRBush extends RBush {
toBBox([x, y]) { return {minX: x, minY: y, maxX: x, maxY: y}; }
compareMinX(a, b) { return a.x - b.x; }
compareMinY(a, b) { return a.y - b.y; }
}
const tree = new MyRBush();
tree.insert([20, 50]); // accepts [x, y] points
```
If you're indexing a static list of points (you don't need to add/remove points after indexing), you should use [kdbush](https://github.com/mourner/kdbush) which performs point indexing 5-8x faster than RBush.
### Bulk-Inserting Data
Bulk-insert the given data into the tree:
```js
tree.load([item1, item2, ...]);
```
Bulk insertion is usually ~2-3 times faster than inserting items one by one.
After bulk loading (bulk insertion into an empty tree),
subsequent query performance is also ~20-30% better.
Note that when you do bulk insertion into an existing tree,
it bulk-loads the given data into a separate tree
and inserts the smaller tree into the larger tree.
This means that bulk insertion works very well for clustered data
(where items in one update are close to each other),
but makes query performance worse if the data is scattered.
### Search
```js
const result = tree.search({
minX: 40,
minY: 20,
maxX: 80,
maxY: 70
});
```
Returns an array of data items (points or rectangles) that the given bounding box intersects.
Note that the `search` method accepts a bounding box in `{minX, minY, maxX, maxY}` format
regardless of the data format.
```js
const allItems = tree.all();
```
Returns all items of the tree.
### Collisions
```js
const result = tree.collides({minX: 40, minY: 20, maxX: 80, maxY: 70});
```
Returns `true` if there are any items intersecting the given bounding box, otherwise `false`.
### Export and Import
```js
// export data as JSON object
const treeData = tree.toJSON();
// import previously exported data
const tree = rbush(9).fromJSON(treeData);
```
Importing and exporting as JSON allows you to use RBush on both the server (using Node.js) and the browser combined,
e.g. first indexing the data on the server and and then importing the resulting tree data on the client for searching.
Note that the `nodeSize` option passed to the constructor must be the same in both trees for export/import to work properly.
### K-Nearest Neighbors
For "_k_ nearest neighbors around a point" type of queries for RBush,
check out [rbush-knn](https://github.com/mourner/rbush-knn).
## Performance
The following sample performance test was done by generating
random uniformly distributed rectangles of ~0.01% area and setting `maxEntries` to `16`
(see `debug/perf.js` script).
Performed with Node.js v6.2.2 on a Retina Macbook Pro 15 (mid-2012).
Test | RBush | [old RTree](https://github.com/imbcmdth/RTree) | Improvement
---------------------------- | ------ | ------ | ----
insert 1M items one by one | 3.18s | 7.83s | 2.5x
1000 searches of 0.01% area | 0.03s | 0.93s | 30x
1000 searches of 1% area | 0.35s | 2.27s | 6.5x
1000 searches of 10% area | 2.18s | 9.53s | 4.4x
remove 1000 items one by one | 0.02s | 1.18s | 50x
bulk-insert 1M items | 1.25s | n/a | 6.7x
## Algorithms Used
* single insertion: non-recursive R-tree insertion with overlap minimizing split routine from R\*-tree (split is very effective in JS, while other R\*-tree modifications like reinsertion on overflow and overlap minimizing subtree search are too slow and not worth it)
* single deletion: non-recursive R-tree deletion using depth-first tree traversal with free-at-empty strategy (entries in underflowed nodes are not reinserted, instead underflowed nodes are kept in the tree and deleted only when empty, which is a good compromise of query vs removal performance)
* bulk loading: OMT algorithm (Overlap Minimizing Top-down Bulk Loading) combined with FloydRivest selection algorithm
* bulk insertion: STLT algorithm (Small-Tree-Large-Tree)
* search: standard non-recursive R-tree search
## Papers
* [R-trees: a Dynamic Index Structure For Spatial Searching](http://www-db.deis.unibo.it/courses/SI-LS/papers/Gut84.pdf)
* [The R*-tree: An Efficient and Robust Access Method for Points and Rectangles+](http://dbs.mathematik.uni-marburg.de/publications/myPapers/1990/BKSS90.pdf)
* [OMT: Overlap Minimizing Top-down Bulk Loading Algorithm for R-tree](http://ftp.informatik.rwth-aachen.de/Publications/CEUR-WS/Vol-74/files/FORUM_18.pdf)
* [Bulk Insertions into R-Trees Using the Small-Tree-Large-Tree Approach](http://www.cs.arizona.edu/~bkmoon/papers/dke06-bulk.pdf)
* [R-Trees: Theory and Applications (book)](http://www.apress.com/9781852339777)
## Development
```bash
npm install # install dependencies
npm test # lint the code and run tests
npm run perf # run performance benchmarks
npm run cov # report test coverage
```
## Compatibility
RBush should run on Node and all major browsers that support ES5.

View File

@@ -0,0 +1,512 @@
import quickselect from 'quickselect';
export default class RBush {
constructor(maxEntries = 9) {
// max entries in a node is 9 by default; min node fill is 40% for best performance
this._maxEntries = Math.max(4, maxEntries);
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
this.clear();
}
all() {
return this._all(this.data, []);
}
search(bbox) {
let node = this.data;
const result = [];
if (!intersects(bbox, node)) return result;
const toBBox = this.toBBox;
const nodesToSearch = [];
while (node) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const childBBox = node.leaf ? toBBox(child) : child;
if (intersects(bbox, childBBox)) {
if (node.leaf) result.push(child);
else if (contains(bbox, childBBox)) this._all(child, result);
else nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return result;
}
collides(bbox) {
let node = this.data;
if (!intersects(bbox, node)) return false;
const nodesToSearch = [];
while (node) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const childBBox = node.leaf ? this.toBBox(child) : child;
if (intersects(bbox, childBBox)) {
if (node.leaf || contains(bbox, childBBox)) return true;
nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return false;
}
load(data) {
if (!(data && data.length)) return this;
if (data.length < this._minEntries) {
for (let i = 0; i < data.length; i++) {
this.insert(data[i]);
}
return this;
}
// recursively build the tree with the given data from scratch using OMT algorithm
let node = this._build(data.slice(), 0, data.length - 1, 0);
if (!this.data.children.length) {
// save as is if tree is empty
this.data = node;
} else if (this.data.height === node.height) {
// split root if trees have the same height
this._splitRoot(this.data, node);
} else {
if (this.data.height < node.height) {
// swap trees if inserted one is bigger
const tmpNode = this.data;
this.data = node;
node = tmpNode;
}
// insert the small tree into the large tree at appropriate level
this._insert(node, this.data.height - node.height - 1, true);
}
return this;
}
insert(item) {
if (item) this._insert(item, this.data.height - 1);
return this;
}
clear() {
this.data = createNode([]);
return this;
}
remove(item, equalsFn) {
if (!item) return this;
let node = this.data;
const bbox = this.toBBox(item);
const path = [];
const indexes = [];
let i, parent, goingUp;
// depth-first iterative tree traversal
while (node || path.length) {
if (!node) { // go up
node = path.pop();
parent = path[path.length - 1];
i = indexes.pop();
goingUp = true;
}
if (node.leaf) { // check current node
const index = findItem(item, node.children, equalsFn);
if (index !== -1) {
// item found, remove the item and condense tree upwards
node.children.splice(index, 1);
path.push(node);
this._condense(path);
return this;
}
}
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
path.push(node);
indexes.push(i);
i = 0;
parent = node;
node = node.children[0];
} else if (parent) { // go right
i++;
node = parent.children[i];
goingUp = false;
} else node = null; // nothing found
}
return this;
}
toBBox(item) { return item; }
compareMinX(a, b) { return a.minX - b.minX; }
compareMinY(a, b) { return a.minY - b.minY; }
toJSON() { return this.data; }
fromJSON(data) {
this.data = data;
return this;
}
_all(node, result) {
const nodesToSearch = [];
while (node) {
if (node.leaf) result.push(...node.children);
else nodesToSearch.push(...node.children);
node = nodesToSearch.pop();
}
return result;
}
_build(items, left, right, height) {
const N = right - left + 1;
let M = this._maxEntries;
let node;
if (N <= M) {
// reached leaf level; return leaf
node = createNode(items.slice(left, right + 1));
calcBBox(node, this.toBBox);
return node;
}
if (!height) {
// target height of the bulk-loaded tree
height = Math.ceil(Math.log(N) / Math.log(M));
// target number of root entries to maximize storage utilization
M = Math.ceil(N / Math.pow(M, height - 1));
}
node = createNode([]);
node.leaf = false;
node.height = height;
// split the items into M mostly square tiles
const N2 = Math.ceil(N / M);
const N1 = N2 * Math.ceil(Math.sqrt(M));
multiSelect(items, left, right, N1, this.compareMinX);
for (let i = left; i <= right; i += N1) {
const right2 = Math.min(i + N1 - 1, right);
multiSelect(items, i, right2, N2, this.compareMinY);
for (let j = i; j <= right2; j += N2) {
const right3 = Math.min(j + N2 - 1, right2);
// pack each entry recursively
node.children.push(this._build(items, j, right3, height - 1));
}
}
calcBBox(node, this.toBBox);
return node;
}
_chooseSubtree(bbox, node, level, path) {
while (true) {
path.push(node);
if (node.leaf || path.length - 1 === level) break;
let minArea = Infinity;
let minEnlargement = Infinity;
let targetNode;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const area = bboxArea(child);
const enlargement = enlargedArea(bbox, child) - area;
// choose entry with the least area enlargement
if (enlargement < minEnlargement) {
minEnlargement = enlargement;
minArea = area < minArea ? area : minArea;
targetNode = child;
} else if (enlargement === minEnlargement) {
// otherwise choose one with the smallest area
if (area < minArea) {
minArea = area;
targetNode = child;
}
}
}
node = targetNode || node.children[0];
}
return node;
}
_insert(item, level, isNode) {
const bbox = isNode ? item : this.toBBox(item);
const insertPath = [];
// find the best node for accommodating the item, saving all nodes along the path too
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
// put the item into the node
node.children.push(item);
extend(node, bbox);
// split on node overflow; propagate upwards if necessary
while (level >= 0) {
if (insertPath[level].children.length > this._maxEntries) {
this._split(insertPath, level);
level--;
} else break;
}
// adjust bboxes along the insertion path
this._adjustParentBBoxes(bbox, insertPath, level);
}
// split overflowed node into two
_split(insertPath, level) {
const node = insertPath[level];
const M = node.children.length;
const m = this._minEntries;
this._chooseSplitAxis(node, m, M);
const splitIndex = this._chooseSplitIndex(node, m, M);
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
newNode.height = node.height;
newNode.leaf = node.leaf;
calcBBox(node, this.toBBox);
calcBBox(newNode, this.toBBox);
if (level) insertPath[level - 1].children.push(newNode);
else this._splitRoot(node, newNode);
}
_splitRoot(node, newNode) {
// split root node
this.data = createNode([node, newNode]);
this.data.height = node.height + 1;
this.data.leaf = false;
calcBBox(this.data, this.toBBox);
}
_chooseSplitIndex(node, m, M) {
let index;
let minOverlap = Infinity;
let minArea = Infinity;
for (let i = m; i <= M - m; i++) {
const bbox1 = distBBox(node, 0, i, this.toBBox);
const bbox2 = distBBox(node, i, M, this.toBBox);
const overlap = intersectionArea(bbox1, bbox2);
const area = bboxArea(bbox1) + bboxArea(bbox2);
// choose distribution with minimum overlap
if (overlap < minOverlap) {
minOverlap = overlap;
index = i;
minArea = area < minArea ? area : minArea;
} else if (overlap === minOverlap) {
// otherwise choose distribution with minimum area
if (area < minArea) {
minArea = area;
index = i;
}
}
}
return index || M - m;
}
// sorts node children by the best axis for split
_chooseSplitAxis(node, m, M) {
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
const xMargin = this._allDistMargin(node, m, M, compareMinX);
const yMargin = this._allDistMargin(node, m, M, compareMinY);
// if total distributions margin value is minimal for x, sort by minX,
// otherwise it's already sorted by minY
if (xMargin < yMargin) node.children.sort(compareMinX);
}
// total margin of all possible split distributions where each node is at least m full
_allDistMargin(node, m, M, compare) {
node.children.sort(compare);
const toBBox = this.toBBox;
const leftBBox = distBBox(node, 0, m, toBBox);
const rightBBox = distBBox(node, M - m, M, toBBox);
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
for (let i = m; i < M - m; i++) {
const child = node.children[i];
extend(leftBBox, node.leaf ? toBBox(child) : child);
margin += bboxMargin(leftBBox);
}
for (let i = M - m - 1; i >= m; i--) {
const child = node.children[i];
extend(rightBBox, node.leaf ? toBBox(child) : child);
margin += bboxMargin(rightBBox);
}
return margin;
}
_adjustParentBBoxes(bbox, path, level) {
// adjust bboxes along the given tree path
for (let i = level; i >= 0; i--) {
extend(path[i], bbox);
}
}
_condense(path) {
// go through the path, removing empty nodes and updating bboxes
for (let i = path.length - 1, siblings; i >= 0; i--) {
if (path[i].children.length === 0) {
if (i > 0) {
siblings = path[i - 1].children;
siblings.splice(siblings.indexOf(path[i]), 1);
} else this.clear();
} else calcBBox(path[i], this.toBBox);
}
}
}
function findItem(item, items, equalsFn) {
if (!equalsFn) return items.indexOf(item);
for (let i = 0; i < items.length; i++) {
if (equalsFn(item, items[i])) return i;
}
return -1;
}
// calculate node's bbox from bboxes of its children
function calcBBox(node, toBBox) {
distBBox(node, 0, node.children.length, toBBox, node);
}
// min bounding rectangle of node children from k to p-1
function distBBox(node, k, p, toBBox, destNode) {
if (!destNode) destNode = createNode(null);
destNode.minX = Infinity;
destNode.minY = Infinity;
destNode.maxX = -Infinity;
destNode.maxY = -Infinity;
for (let i = k; i < p; i++) {
const child = node.children[i];
extend(destNode, node.leaf ? toBBox(child) : child);
}
return destNode;
}
function extend(a, b) {
a.minX = Math.min(a.minX, b.minX);
a.minY = Math.min(a.minY, b.minY);
a.maxX = Math.max(a.maxX, b.maxX);
a.maxY = Math.max(a.maxY, b.maxY);
return a;
}
function compareNodeMinX(a, b) { return a.minX - b.minX; }
function compareNodeMinY(a, b) { return a.minY - b.minY; }
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
function enlargedArea(a, b) {
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
}
function intersectionArea(a, b) {
const minX = Math.max(a.minX, b.minX);
const minY = Math.max(a.minY, b.minY);
const maxX = Math.min(a.maxX, b.maxX);
const maxY = Math.min(a.maxY, b.maxY);
return Math.max(0, maxX - minX) *
Math.max(0, maxY - minY);
}
function contains(a, b) {
return a.minX <= b.minX &&
a.minY <= b.minY &&
b.maxX <= a.maxX &&
b.maxY <= a.maxY;
}
function intersects(a, b) {
return b.minX <= a.maxX &&
b.minY <= a.maxY &&
b.maxX >= a.minX &&
b.maxY >= a.minY;
}
function createNode(children) {
return {
children,
height: 1,
leaf: true,
minX: Infinity,
minY: Infinity,
maxX: -Infinity,
maxY: -Infinity
};
}
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
// combines selection algorithm with binary divide & conquer approach
function multiSelect(arr, left, right, n, compare) {
const stack = [left, right];
while (stack.length) {
right = stack.pop();
left = stack.pop();
if (right - left <= n) continue;
const mid = left + Math.ceil((right - left) / n / 2) * n;
quickselect(arr, mid, left, right, compare);
stack.push(left, mid, mid, right);
}
}

View File

@@ -0,0 +1,56 @@
{
"name": "rbush",
"version": "3.0.1",
"description": "High-performance 2D spatial index for rectangles (based on R*-tree with bulk loading and bulk insertion algorithms)",
"homepage": "https://github.com/mourner/rbush",
"repository": {
"type": "git",
"url": "git://github.com/mourner/rbush.git"
},
"keywords": [
"spatial",
"tree",
"search",
"rectangle",
"index",
"math"
],
"author": "Vladimir Agafonkin",
"license": "MIT",
"main": "rbush.js",
"module": "index.js",
"browser": "rbush.min.js",
"jsdelivr": "rbush.min.js",
"unpkg": "rbush.min.js",
"devDependencies": {
"benchmark": "^2.1.4",
"c8": "^5.0.1",
"eslint": "^6.1.0",
"eslint-config-mourner": "^3.0.0",
"esm": "^3.2.25",
"rollup": "^1.17.0",
"rollup-plugin-buble": "^0.19.8",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^5.1.1",
"tape": "^4.11.0"
},
"scripts": {
"pretest": "eslint index.js test/test.js bench/*.js",
"test": "tape -r esm test/test.js",
"perf": "node -r esm ./bench/perf.js",
"cov": "c8 npm run test",
"build": "rollup -c",
"prepublishOnly": "npm run build"
},
"files": [
"index.js",
"rbush.js",
"rbush.min.js"
],
"eslintConfig": {
"extends": "mourner"
},
"dependencies": {
"quickselect": "^2.0.0"
}
}

View File

@@ -0,0 +1,574 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.RBush = factory());
}(this, function () { 'use strict';
function quickselect(arr, k, left, right, compare) {
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
}
function quickselectStep(arr, k, left, right, compare) {
while (right > left) {
if (right - left > 600) {
var n = right - left + 1;
var m = k - left + 1;
var z = Math.log(n);
var s = 0.5 * Math.exp(2 * z / 3);
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
quickselectStep(arr, k, newLeft, newRight, compare);
}
var t = arr[k];
var i = left;
var j = right;
swap(arr, left, k);
if (compare(arr[right], t) > 0) { swap(arr, left, right); }
while (i < j) {
swap(arr, i, j);
i++;
j--;
while (compare(arr[i], t) < 0) { i++; }
while (compare(arr[j], t) > 0) { j--; }
}
if (compare(arr[left], t) === 0) { swap(arr, left, j); }
else {
j++;
swap(arr, j, right);
}
if (j <= k) { left = j + 1; }
if (k <= j) { right = j - 1; }
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function defaultCompare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
var RBush = function RBush(maxEntries) {
if ( maxEntries === void 0 ) maxEntries = 9;
// max entries in a node is 9 by default; min node fill is 40% for best performance
this._maxEntries = Math.max(4, maxEntries);
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
this.clear();
};
RBush.prototype.all = function all () {
return this._all(this.data, []);
};
RBush.prototype.search = function search (bbox) {
var node = this.data;
var result = [];
if (!intersects(bbox, node)) { return result; }
var toBBox = this.toBBox;
var nodesToSearch = [];
while (node) {
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
var childBBox = node.leaf ? toBBox(child) : child;
if (intersects(bbox, childBBox)) {
if (node.leaf) { result.push(child); }
else if (contains(bbox, childBBox)) { this._all(child, result); }
else { nodesToSearch.push(child); }
}
}
node = nodesToSearch.pop();
}
return result;
};
RBush.prototype.collides = function collides (bbox) {
var node = this.data;
if (!intersects(bbox, node)) { return false; }
var nodesToSearch = [];
while (node) {
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
var childBBox = node.leaf ? this.toBBox(child) : child;
if (intersects(bbox, childBBox)) {
if (node.leaf || contains(bbox, childBBox)) { return true; }
nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return false;
};
RBush.prototype.load = function load (data) {
if (!(data && data.length)) { return this; }
if (data.length < this._minEntries) {
for (var i = 0; i < data.length; i++) {
this.insert(data[i]);
}
return this;
}
// recursively build the tree with the given data from scratch using OMT algorithm
var node = this._build(data.slice(), 0, data.length - 1, 0);
if (!this.data.children.length) {
// save as is if tree is empty
this.data = node;
} else if (this.data.height === node.height) {
// split root if trees have the same height
this._splitRoot(this.data, node);
} else {
if (this.data.height < node.height) {
// swap trees if inserted one is bigger
var tmpNode = this.data;
this.data = node;
node = tmpNode;
}
// insert the small tree into the large tree at appropriate level
this._insert(node, this.data.height - node.height - 1, true);
}
return this;
};
RBush.prototype.insert = function insert (item) {
if (item) { this._insert(item, this.data.height - 1); }
return this;
};
RBush.prototype.clear = function clear () {
this.data = createNode([]);
return this;
};
RBush.prototype.remove = function remove (item, equalsFn) {
if (!item) { return this; }
var node = this.data;
var bbox = this.toBBox(item);
var path = [];
var indexes = [];
var i, parent, goingUp;
// depth-first iterative tree traversal
while (node || path.length) {
if (!node) { // go up
node = path.pop();
parent = path[path.length - 1];
i = indexes.pop();
goingUp = true;
}
if (node.leaf) { // check current node
var index = findItem(item, node.children, equalsFn);
if (index !== -1) {
// item found, remove the item and condense tree upwards
node.children.splice(index, 1);
path.push(node);
this._condense(path);
return this;
}
}
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
path.push(node);
indexes.push(i);
i = 0;
parent = node;
node = node.children[0];
} else if (parent) { // go right
i++;
node = parent.children[i];
goingUp = false;
} else { node = null; } // nothing found
}
return this;
};
RBush.prototype.toBBox = function toBBox (item) { return item; };
RBush.prototype.compareMinX = function compareMinX (a, b) { return a.minX - b.minX; };
RBush.prototype.compareMinY = function compareMinY (a, b) { return a.minY - b.minY; };
RBush.prototype.toJSON = function toJSON () { return this.data; };
RBush.prototype.fromJSON = function fromJSON (data) {
this.data = data;
return this;
};
RBush.prototype._all = function _all (node, result) {
var nodesToSearch = [];
while (node) {
if (node.leaf) { result.push.apply(result, node.children); }
else { nodesToSearch.push.apply(nodesToSearch, node.children); }
node = nodesToSearch.pop();
}
return result;
};
RBush.prototype._build = function _build (items, left, right, height) {
var N = right - left + 1;
var M = this._maxEntries;
var node;
if (N <= M) {
// reached leaf level; return leaf
node = createNode(items.slice(left, right + 1));
calcBBox(node, this.toBBox);
return node;
}
if (!height) {
// target height of the bulk-loaded tree
height = Math.ceil(Math.log(N) / Math.log(M));
// target number of root entries to maximize storage utilization
M = Math.ceil(N / Math.pow(M, height - 1));
}
node = createNode([]);
node.leaf = false;
node.height = height;
// split the items into M mostly square tiles
var N2 = Math.ceil(N / M);
var N1 = N2 * Math.ceil(Math.sqrt(M));
multiSelect(items, left, right, N1, this.compareMinX);
for (var i = left; i <= right; i += N1) {
var right2 = Math.min(i + N1 - 1, right);
multiSelect(items, i, right2, N2, this.compareMinY);
for (var j = i; j <= right2; j += N2) {
var right3 = Math.min(j + N2 - 1, right2);
// pack each entry recursively
node.children.push(this._build(items, j, right3, height - 1));
}
}
calcBBox(node, this.toBBox);
return node;
};
RBush.prototype._chooseSubtree = function _chooseSubtree (bbox, node, level, path) {
while (true) {
path.push(node);
if (node.leaf || path.length - 1 === level) { break; }
var minArea = Infinity;
var minEnlargement = Infinity;
var targetNode = (void 0);
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
var area = bboxArea(child);
var enlargement = enlargedArea(bbox, child) - area;
// choose entry with the least area enlargement
if (enlargement < minEnlargement) {
minEnlargement = enlargement;
minArea = area < minArea ? area : minArea;
targetNode = child;
} else if (enlargement === minEnlargement) {
// otherwise choose one with the smallest area
if (area < minArea) {
minArea = area;
targetNode = child;
}
}
}
node = targetNode || node.children[0];
}
return node;
};
RBush.prototype._insert = function _insert (item, level, isNode) {
var bbox = isNode ? item : this.toBBox(item);
var insertPath = [];
// find the best node for accommodating the item, saving all nodes along the path too
var node = this._chooseSubtree(bbox, this.data, level, insertPath);
// put the item into the node
node.children.push(item);
extend(node, bbox);
// split on node overflow; propagate upwards if necessary
while (level >= 0) {
if (insertPath[level].children.length > this._maxEntries) {
this._split(insertPath, level);
level--;
} else { break; }
}
// adjust bboxes along the insertion path
this._adjustParentBBoxes(bbox, insertPath, level);
};
// split overflowed node into two
RBush.prototype._split = function _split (insertPath, level) {
var node = insertPath[level];
var M = node.children.length;
var m = this._minEntries;
this._chooseSplitAxis(node, m, M);
var splitIndex = this._chooseSplitIndex(node, m, M);
var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
newNode.height = node.height;
newNode.leaf = node.leaf;
calcBBox(node, this.toBBox);
calcBBox(newNode, this.toBBox);
if (level) { insertPath[level - 1].children.push(newNode); }
else { this._splitRoot(node, newNode); }
};
RBush.prototype._splitRoot = function _splitRoot (node, newNode) {
// split root node
this.data = createNode([node, newNode]);
this.data.height = node.height + 1;
this.data.leaf = false;
calcBBox(this.data, this.toBBox);
};
RBush.prototype._chooseSplitIndex = function _chooseSplitIndex (node, m, M) {
var index;
var minOverlap = Infinity;
var minArea = Infinity;
for (var i = m; i <= M - m; i++) {
var bbox1 = distBBox(node, 0, i, this.toBBox);
var bbox2 = distBBox(node, i, M, this.toBBox);
var overlap = intersectionArea(bbox1, bbox2);
var area = bboxArea(bbox1) + bboxArea(bbox2);
// choose distribution with minimum overlap
if (overlap < minOverlap) {
minOverlap = overlap;
index = i;
minArea = area < minArea ? area : minArea;
} else if (overlap === minOverlap) {
// otherwise choose distribution with minimum area
if (area < minArea) {
minArea = area;
index = i;
}
}
}
return index || M - m;
};
// sorts node children by the best axis for split
RBush.prototype._chooseSplitAxis = function _chooseSplitAxis (node, m, M) {
var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
var compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
var xMargin = this._allDistMargin(node, m, M, compareMinX);
var yMargin = this._allDistMargin(node, m, M, compareMinY);
// if total distributions margin value is minimal for x, sort by minX,
// otherwise it's already sorted by minY
if (xMargin < yMargin) { node.children.sort(compareMinX); }
};
// total margin of all possible split distributions where each node is at least m full
RBush.prototype._allDistMargin = function _allDistMargin (node, m, M, compare) {
node.children.sort(compare);
var toBBox = this.toBBox;
var leftBBox = distBBox(node, 0, m, toBBox);
var rightBBox = distBBox(node, M - m, M, toBBox);
var margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
for (var i = m; i < M - m; i++) {
var child = node.children[i];
extend(leftBBox, node.leaf ? toBBox(child) : child);
margin += bboxMargin(leftBBox);
}
for (var i$1 = M - m - 1; i$1 >= m; i$1--) {
var child$1 = node.children[i$1];
extend(rightBBox, node.leaf ? toBBox(child$1) : child$1);
margin += bboxMargin(rightBBox);
}
return margin;
};
RBush.prototype._adjustParentBBoxes = function _adjustParentBBoxes (bbox, path, level) {
// adjust bboxes along the given tree path
for (var i = level; i >= 0; i--) {
extend(path[i], bbox);
}
};
RBush.prototype._condense = function _condense (path) {
// go through the path, removing empty nodes and updating bboxes
for (var i = path.length - 1, siblings = (void 0); i >= 0; i--) {
if (path[i].children.length === 0) {
if (i > 0) {
siblings = path[i - 1].children;
siblings.splice(siblings.indexOf(path[i]), 1);
} else { this.clear(); }
} else { calcBBox(path[i], this.toBBox); }
}
};
function findItem(item, items, equalsFn) {
if (!equalsFn) { return items.indexOf(item); }
for (var i = 0; i < items.length; i++) {
if (equalsFn(item, items[i])) { return i; }
}
return -1;
}
// calculate node's bbox from bboxes of its children
function calcBBox(node, toBBox) {
distBBox(node, 0, node.children.length, toBBox, node);
}
// min bounding rectangle of node children from k to p-1
function distBBox(node, k, p, toBBox, destNode) {
if (!destNode) { destNode = createNode(null); }
destNode.minX = Infinity;
destNode.minY = Infinity;
destNode.maxX = -Infinity;
destNode.maxY = -Infinity;
for (var i = k; i < p; i++) {
var child = node.children[i];
extend(destNode, node.leaf ? toBBox(child) : child);
}
return destNode;
}
function extend(a, b) {
a.minX = Math.min(a.minX, b.minX);
a.minY = Math.min(a.minY, b.minY);
a.maxX = Math.max(a.maxX, b.maxX);
a.maxY = Math.max(a.maxY, b.maxY);
return a;
}
function compareNodeMinX(a, b) { return a.minX - b.minX; }
function compareNodeMinY(a, b) { return a.minY - b.minY; }
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
function enlargedArea(a, b) {
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
}
function intersectionArea(a, b) {
var minX = Math.max(a.minX, b.minX);
var minY = Math.max(a.minY, b.minY);
var maxX = Math.min(a.maxX, b.maxX);
var maxY = Math.min(a.maxY, b.maxY);
return Math.max(0, maxX - minX) *
Math.max(0, maxY - minY);
}
function contains(a, b) {
return a.minX <= b.minX &&
a.minY <= b.minY &&
b.maxX <= a.maxX &&
b.maxY <= a.maxY;
}
function intersects(a, b) {
return b.minX <= a.maxX &&
b.minY <= a.maxY &&
b.maxX >= a.minX &&
b.maxY >= a.minY;
}
function createNode(children) {
return {
children: children,
height: 1,
leaf: true,
minX: Infinity,
minY: Infinity,
maxX: -Infinity,
maxY: -Infinity
};
}
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
// combines selection algorithm with binary divide & conquer approach
function multiSelect(arr, left, right, n, compare) {
var stack = [left, right];
while (stack.length) {
right = stack.pop();
left = stack.pop();
if (right - left <= n) { continue; }
var mid = left + Math.ceil((right - left) / n / 2) * n;
quickselect(arr, mid, left, right, compare);
stack.push(left, mid, mid, right);
}
}
return RBush;
}));

File diff suppressed because one or more lines are too long

50
frontend/node_modules/geojson-rbush/package.json generated vendored Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "geojson-rbush",
"version": "3.2.0",
"description": "GeoJSON implementation of RBush",
"main": "index",
"types": "index.d.ts",
"files": [
"index.js",
"index.d.ts"
],
"scripts": {
"pretest": "tsc types.ts",
"test": "node test.js --coverage",
"bench": "node bench.js",
"docs": "documentation readme index.js --section=API"
},
"keywords": [
"geojson",
"index",
"tree",
"spatial",
"rbush"
],
"author": "Denis Carriere <@DenisCarriere>",
"contributors": [
"Vladimir Agafonkin <@mourner>",
"Denis Carriere <@DenisCarriere>",
"Jordan Rousseau <@jvrousseau>"
],
"license": "MIT",
"devDependencies": {
"@turf/bbox-polygon": "*",
"@turf/random": "*",
"@types/node": "*",
"benchmark": "*",
"documentation": "*",
"load-json-file": "*",
"tap": "*",
"tape": "*",
"typescript": "*",
"write-json-file": "*"
},
"dependencies": {
"@turf/bbox": "*",
"@turf/helpers": "6.x",
"@turf/meta": "6.x",
"@types/geojson": "7946.0.8",
"rbush": "^3.0.1"
}
}