You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
6.3 KiB

const express = require('express');
const bodyParser = require('body-parser');
const MTree = require('./m-tree/mtree');
const Node = require('./m-tree/nodes').Node;
const distanceFunctions = require('./m-tree/distance-functions');
// Generator used to generate random points for the MTree
const Generator = require('./data/generator');
// Express app
const app = express();
app.use(bodyParser.json({ limit: '100mb' }));
// Create an MTree with the given dimensions and capacity, using the Euclidean distance
let mtree = null;
// Serve static files from the 'public' directory
app.use(express.static(__dirname + '/public'));
// Respond to favicon requests with no content
app.get('/favicon.ico', (req, res) => { res.status(204).end(); });
// Serve the index.html file
app.get('/', (req, res) => { res.sendFile(__dirname + '/public/index.html'); });
// Return the MTree
app.get('/tree', (req, res) => {
const tree = JSON.parse(JSON.stringify(mtree, (key, value) => {
if (key === 'parent' || key === 'mtree')
return value && value.id;
return value;
}));
const distanceFunctionName = Object.keys(distanceFunctions).find(key => distanceFunctions[key] === mtree.distanceFunction);
tree.distanceFunctionName = distanceFunctionName;
res.send(tree);
});
// Endpoint to check if the MTree has been initialized
app.get('/isInitialized', (req, res) => { res.send(!!mtree); });
// Return the list of supported distance functions
app.get('/distanceFunctions', (req, res) => { res.send(Object.keys(distanceFunctions)); });
// Insert a point into the MTree
app.post('/insert', (req, res) => {
let point = req.body.point;
if (!point)
return res.status(400).send('Missing point');
if (point.length !== mtree.dimension)
return res.status(400).send('Point has the wrong dimension');
mtree.insert(point);
res.send('Point inserted');
});
// Perform a range query on the MTree
app.get('/rangeQuery', (req, res) => {
const { queryPoint, radius } = req.query;
if (!queryPoint)
return res.status(400).send('Missing query point');
if (!radius || isNaN(radius))
return res.status(400).send('Missing radius');
if (JSON.parse(queryPoint).length !== mtree.dimension)
return res.status(400).send('Query point has the wrong dimension');
let parsedQueryPoint;
try {
parsedQueryPoint = JSON.parse(queryPoint);
} catch (e) {
return res.status(400).send('Invalid query point');
}
const parsedRadius = parseFloat(radius);
if (parsedQueryPoint.length !== mtree.dimension)
return res.status(400).send('Query point has the wrong dimension');
console.time('mtreeRangeQuery');
const result = mtree.rangeQuery(parsedQueryPoint, parsedRadius);
console.timeEnd('mtreeRangeQuery');
//console.log(result.length);
console.time('sequentialSearch');
const sequentialResult = points.filter(point => mtree.distanceFunction(point, parsedQueryPoint) <= parsedRadius);
console.timeEnd('sequentialSearch');
//console.log(sequentialResult.length);
res.send(result);
});
// Perform a k-NN query on the MTree
app.get('/kNNQuery', (req, res) => {
const { queryPoint, k } = req.query;
if (!queryPoint)
return res.status(400).send('Missing query point');
if (!k || isNaN(k))
return res.status(400).send('Missing k');
if (JSON.parse(queryPoint).length !== mtree.dimension)
return res.status(400).send('Query point has the wrong dimension');
let parsedQueryPoint;
try {
parsedQueryPoint = JSON.parse(queryPoint);
} catch (e) {
return res.status(400).send('Invalid query point');
}
const kInt = parseInt(k, 10);
console.time('mtreeKNNQuery');
const result = mtree.kNNQuery(parsedQueryPoint, kInt);
console.timeEnd('mtreeKNNQuery');
//console.log(result);
console.time('sequentialSearch');
const sequentialResult = points.sort((a, b) => mtree.distanceFunction(a, parsedQueryPoint) - mtree.distanceFunction(b, parsedQueryPoint)).slice(0, kInt);
console.timeEnd('sequentialSearch');
//const sequentialResultWithDistance = sequentialResult.map(point => ({point, distance: mtree.distanceFunction(point, parsedQueryPoint)}));
//console.log(sequentialResult);
res.send(result);
});
// Recreate the MTree with the given dimensions
app.post('/recreate', (req, res) => {
let { dimensions, pointCount, capacity, distanceFunction } = req.body;
dimensions = parseInt(dimensions);
pointCount = parseInt(pointCount);
capacity = parseInt(capacity);
console.log(dimensions, pointCount, capacity, distanceFunction);
if (isNaN(dimensions) || dimensions <= 0)
return res.status(400).send('Invalid dimensions');
if (isNaN(pointCount) || pointCount <= 0)
return res.status(400).send('Invalid point count');
if (isNaN(capacity) || capacity <= 0)
return res.status(400).send('Invalid capacity');
const distanceFunctionName = distanceFunction.toLowerCase();
const chosenDistanceFunction = distanceFunctions[distanceFunctionName];
if (!chosenDistanceFunction)
return res.status(400).send(`Invalid distance function. Supported functions: ${Object.keys(distanceFunctions).join(', ')}`);
const generator = new Generator(dimensions);
mtree = new MTree(dimensions, capacity, chosenDistanceFunction);
points = generator.generateMany(pointCount);
// points.forEach(point => mtree.insert(point));
console.time('tree creation')
points.forEach((point, i) => {
if (i % 1000 === 0) {
console.time(`insertion of next 1000 points`);
console.log(`inserted ${i} points`);
}
mtree.insert(point);
if (i % 1000 === 999)
console.timeEnd(`insertion of next 1000 points`);
})
res.send('MTree recreated');
console.timeEnd('tree creation')
});
// Load the MTree from the posted JSON
app.post('/loadTree', (req, res) => {
const tree = req.body;
if (!tree)
return res.status(400).send('Missing tree');
try {
mtree = MTree.fromJSON(tree);
} catch (e) {
return res.status(400).send('Invalid tree');
}
points = Node.findGroundEntries(tree.root).map(entry => entry.point);
res.send('MTree loaded');
});
// Start the server
app.listen(3000, () => { console.log('MTree API is running on port 3000'); });

Powered by TurnKey Linux.