it('should choose an insertion node based on minimizing enlargement, then area', assert => { const firstPlane = new GeoPlane({ maxChildren: 4 }); [ [0, 0, 5, 5], [20, 20, 5, 5], [0, 0, 5, 10], [5, 5, 5, 5], [0, 0, 10, 5], [25, 20, 1, 5], [10, 0, 1, 10] ].forEach(rectangle => firstPlane.insert(new GeoRectangle(...rectangle))); assert.deepEqual(firstPlane.exportBoundingBoxTreeForTesting(), { boundingBox: [0, 0, 26, 25], height: 2, children: [ { boundingBox: [0, 0, 11, 10], height: 1, children: [ [0, 0, 5, 5], [0, 0, 5, 10], [0, 0, 10, 5], [10, 0, 11, 10] ] }, { boundingBox: [5, 5, 26, 25], height: 1, children: [ [5, 5, 10, 10], [20, 20, 25, 25], [25, 20, 26, 25] ] }, ] }); // Classical T-shape split representing Figure 3.1 from the paper. const secondPlane = new GeoPlane({ maxChildren: 3, minChildren: 2 }); [ [0, 10, 40, 10], [30, 0, 10, 200], [50, 0, 10, 200], [50, 10, 40, 10] ].forEach(rectangle => secondPlane.insert(new GeoRectangle(...rectangle))); assert.deepEqual(secondPlane.exportBoundingBoxTreeForTesting(), { boundingBox: [0, 0, 90, 200], height: 2, children: [ { boundingBox: [30, 0, 60, 200], height: 1, children: [ [30, 0, 40, 200], [50, 0, 60, 200] ] }, { boundingBox: [0, 10, 90, 20], height: 1, children: [ [0, 10, 40, 20], [50, 10, 90, 20] ] } ] }); });
it('should be able to find points that intersect with a rectangle', assert => { const plane = new GeoPlane({ maxChildren: 4 }), points = {}; referencePoints.forEach(position => { const point = new GeoPoint(...position); points[point.x] = points[point.x] || {}; points[point.x][point.y] = point; plane.insert(point); }); assert.deepEqual(plane.intersect([40, 20, 80, 70]), [ points[70][70], points[45][70], points[60][60], points[75][25], points[70][20], points[50][25], points[45][20], points[75][50], points[70][45], points[60][35], points[45][45], points[50][50] ]); assert.deepEqual(plane.intersect([0, 0, 10, 100]), [ points[0][75], points[10][60], points[10][85], points[0][25], points[10][10], points[0][0], points[10][35], points[0][50] ]); assert.deepEqual(plane.intersect([60, 60, 75, 75]), [ points[70][70], points[75][75], points[60][60] ]); });
it('should split the tree when reaching the maximum number of children in a node', assert => { const plane = new GeoPlane(); for (let i = 0; i < plane.maxChildren + 1; ++i) plane.insert(createRectangle()); assert.equal(plane.height, 2); });
it('should adjust the bounding box on object modification', assert => { const plane = new GeoPlane(); plane.insert(new GeoRectangle(10, 15, 10, 10)); assert.deepEqual(plane.boundingBox, [10, 15, 20, 25]); plane.insert(new GeoCircle(10, 10, 5)); assert.deepEqual(plane.boundingBox, [5, 5, 20, 25]); });
referencePoints.forEach(position => { const point = new GeoPoint(...position); points[point.x] = points[point.x] || {}; points[point.x][point.y] = point; plane.insert(point); });
it('should be able to find the nearest neighbours to a point', assert => { const plane = new GeoPlane({ maxChildren: 4 }), points = {}; referencePoints.forEach(position => { const point = new GeoPoint(...position); points[point.x] = points[point.x] || {}; points[point.x][point.y] = point; plane.insert(point); }); assert.deepEqual(plane.nearest([50, 50], 5), [ points[50][50], points[45][45], points[35][35], points[60][60], points[60][35] ]); });
const createRectangle = (x, y, w, h) => { const rectangle = new GeoRectangle(x, y, w, h); rectangles[rectangle.x] = rectangles[rectangle.x] || {}; rectangles[rectangle.x][rectangle.y] = rectangle; plane.insert(rectangle); };
const insertRectangles = (width, height) => { depth++; for (let x = MAP_BOUNDARIES[0]; x < MAP_BOUNDARIES[2]; x += width) { for (let y = MAP_BOUNDARIES[1]; y < MAP_BOUNDARIES[3]; y += height) { plane.insert(new GeoRectangle(x, y, width, height)); counter++; } } };
].forEach(rectangle => secondPlane.insert(new GeoRectangle(...rectangle)));
].forEach(rectangle => firstPlane.insert(new GeoRectangle(...rectangle)));
it('should be able to perform rectangles-for-point operations quickly', assert => { const IS_PERFORMANCE_TEST = false; // This test acts as a performance test to measure how long insertion operations take for large // numbers of rectangles, as well as find operations for finding intersecting rectangles for a // given point. Note that finding rectangles for rectangles would be equally fast. const MAP_BOUNDARIES = [ -3000, -3000, 3000, 3000 ], INTERSECT_ITERATIONS = 100000, NEAREST_ITERATIONS = 1000, NEAREST_COUNT = 100; let plane = new GeoPlane(), counter = 0, depth = 0; // Inserts a series of rectangles in |plane| of size [|width|, |height|]. const insertRectangles = (width, height) => { depth++; for (let x = MAP_BOUNDARIES[0]; x < MAP_BOUNDARIES[2]; x += width) { for (let y = MAP_BOUNDARIES[1]; y < MAP_BOUNDARIES[3]; y += height) { plane.insert(new GeoRectangle(x, y, width, height)); counter++; } } }; const insertionBegin = highResolutionTime(); insertRectangles(1000, 1000); // 36 insertRectangles( 600, 600); // 100 insertRectangles( 300, 300); // 400 insertRectangles( 100, 100); // 3600 if (IS_PERFORMANCE_TEST) { insertRectangles( 50, 50); // 14400 insertRectangles( 25, 25); // 57600 } const insertionEnd = highResolutionTime(); const intersectBegin = highResolutionTime(); for (let iteration = 0; iteration < INTERSECT_ITERATIONS; ++iteration) { const x = Math.random() * MAP_BOUNDARIES[2] + MAP_BOUNDARIES[0]; const y = Math.random() * MAP_BOUNDARIES[3] + MAP_BOUNDARIES[1]; assert.equal(plane.intersect([x, y, x, y]).length, depth); } const intersectEnd = highResolutionTime(); const nearestBegin = highResolutionTime(); for (let iteration = 0; iteration < NEAREST_ITERATIONS; ++iteration) { const x = Math.random() * MAP_BOUNDARIES[2] + MAP_BOUNDARIES[0]; const y = Math.random() * MAP_BOUNDARIES[3] + MAP_BOUNDARIES[1]; assert.equal(plane.nearest([x, y], NEAREST_COUNT).length, Math.min(counter, NEAREST_COUNT)); } const nearestEnd = highResolutionTime(); const insertionTime = Math.round((insertionEnd - insertionBegin) * 100) / 100, intersectTime = Math.round((intersectEnd - intersectBegin) * 100) / 100, nearestTime = Math.round((nearestEnd - nearestBegin) * 100) / 100; console.log('[GeoPlane] Insertion (' + counter + '): ' + insertionTime + 'ms; intersect (' + INTERSECT_ITERATIONS + '): ' + intersectTime + 'ms; nearest (' + NEAREST_ITERATIONS + ', ' + NEAREST_COUNT + '): ' + nearestTime + 'ms'); });
it('should be able to find rectangles that intersect with a given point', assert => { const plane = new GeoPlane({ maxChildren: 4 }), rectangles = {}; // Creates a rectangle with the given size, stores it in |points| and adds it to the |plane|. const createRectangle = (x, y, w, h) => { const rectangle = new GeoRectangle(x, y, w, h); rectangles[rectangle.x] = rectangles[rectangle.x] || {}; rectangles[rectangle.x][rectangle.y] = rectangle; plane.insert(rectangle); }; for (let x = 0; x < 100; x += 10) { for (let y = 0; y < 100; y += 10) createRectangle(x, y, 10, 10); } for (let x = 1; x < 100; x += 15) { for (let y = 1; y < 100; y += 15) createRectangle(x, y, 15, 15); } for (let x = 2; x < 100; x += 20) { for (let y = 2; y < 100; y += 20) createRectangle(x, y, 20, 20); } // Utility function to sort an array of rectangles. const sortRectangles = rectangles => { return rectangles.sort((lhs, rhs) => { if (lhs.x == rhs.x) return lhs.y < rhs.y ? -1 : 1; return lhs.x < rhs.x ? -1 : 1; }); } // Test with a hundred random points, that also do a linear search over the registered |points| // in order to figure out whether they're on the plane or not. for (let attempt = 0; attempt < 100; ++attempt) { const point = new GeoPoint(Math.floor(Math.random() * 100), Math.floor(Math.random() * 100)), actual = plane.intersect([point.x, point.y, point.x, point.y]), expected = []; for (let x in rectangles) { for (let y in rectangles[x]) { const boundingBox = rectangles[x][y].boundingBox(); if (point.x < boundingBox[0] || point.y < boundingBox[1] || point.x > boundingBox[2] || point.y > boundingBox[3]) continue; expected.push(rectangles[x][y]); } } // Sort both the |expected| array and the results of |plane.intersect()| to make sure that the // deepEqual comparison is done on arrays of equal order. assert.deepEqual(sortRectangles(actual), sortRectangles(expected)); } });