From 0861fbe78e18912d1caf4dd8568859b766661e31 Mon Sep 17 00:00:00 2001 From: Will Forma Date: Mon, 29 Jan 2018 17:15:36 +0000 Subject: [PATCH 1/4] Fix for floating point precision errors when dealing with small values and searching for exact points. --- dist/rtree.js | 11 +++++++---- lib/rectangle.js | 10 +++++++--- test/searchexact.test.js | 24 ++++++++++++++++++++++++ test/searching.test.js | 3 ++- 4 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 test/searchexact.test.js diff --git a/dist/rtree.js b/dist/rtree.js index 028b780..e282b13 100644 --- a/dist/rtree.js +++ b/dist/rtree.js @@ -328,9 +328,12 @@ function Rectangle(x, y, w, h) { // new Rectangle(bounds) or new Rectangle(x, y, */ Rectangle.overlapRectangle = function (a, b) { //if(!((a.h||a.w)&&(b.h||b.w))){ not faster resist the urge! - if ((a.h === 0 && a.w === 0) || (b.h === 0 && b.w === 0)) { - return a.x <= (b.x + b.w) && (a.x + a.w) >= b.x && a.y <= (b.y + b.h) && (a.y + a.h) >= b.y; - } + var eps = 1e-6; + if ((a.h < eps && a.w < eps) || (b.h < eps && b.w < eps)) { + return (a.x - (b.x + b.w) < eps) && + ((a.x + a.w) - b.x > -eps) && + (a.y - (b.y + b.h) < eps) && + ((a.y + a.h) - b.y > -eps); else { return a.x < (b.x + b.w) && (a.x + a.w) > b.x && a.y < (b.y + b.h) && (a.y + a.h) > b.y; } @@ -908,4 +911,4 @@ if (typeof Array.isArray !== 'function') { },{"./rectangle":3}]},{},[2]) (2) -}); \ No newline at end of file +}); diff --git a/lib/rectangle.js b/lib/rectangle.js index e52ab77..2d1c984 100644 --- a/lib/rectangle.js +++ b/lib/rectangle.js @@ -104,8 +104,12 @@ function Rectangle(x, y, w, h) { // new Rectangle(bounds) or new Rectangle(x, y, */ Rectangle.overlapRectangle = function (a, b) { //if(!((a.h||a.w)&&(b.h||b.w))){ not faster resist the urge! - if ((a.h === 0 && a.w === 0) || (b.h === 0 && b.w === 0)) { - return a.x <= (b.x + b.w) && (a.x + a.w) >= b.x && a.y <= (b.y + b.h) && (a.y + a.h) >= b.y; + var eps = 1e-6 + if ((a.h < eps && a.w < eps) || (b.h < eps && b.w < eps)) { + return (a.x - (b.x + b.w) < eps) && + ((a.x + a.w) - b.x > -eps) && + (a.y - (b.y + b.h) < eps) && + ((a.y + a.h) - b.y > -eps); } else { return a.x < (b.x + b.w) && (a.x + a.w) > b.x && a.y < (b.y + b.h) && (a.y + a.h) > b.y; @@ -196,4 +200,4 @@ Rectangle.squarifiedRatio = function (l, w, fill) { var lgeo = larea / (lperi * lperi); return larea * fill / lgeo; }; -module.exports = Rectangle; \ No newline at end of file +module.exports = Rectangle; diff --git a/test/searchexact.test.js b/test/searchexact.test.js new file mode 100644 index 0000000..5b8799b --- /dev/null +++ b/test/searchexact.test.js @@ -0,0 +1,24 @@ +var test = require('tape'); +var RTree = require('../lib'); +test('RTree Searching for exact point', function(t) { + var rt = new RTree(); + t.plan(1); + var data = []; + for(var i = 0; i < 1000; i++) { + data[i] = {x: Math.random() * 10, y: Math.random() * 10, w: 0, h: 0}; + rt.insert(data[i], {}); + } + + var len = 0; + var bounds; + for(var i = 0; i < 1000; i++) { + bounds = { + x: data[i].x, + y: data[i].y, + w: 0, + h: 0 + }; + len += rt.search(bounds).length; + } + t.equal(len, 1000, '1k In-Bounds Searches'); +}); diff --git a/test/searching.test.js b/test/searching.test.js index add302e..c9572a2 100644 --- a/test/searching.test.js +++ b/test/searching.test.js @@ -24,6 +24,7 @@ test('RTree Searching', function(t) { i--; } t.equals(len, 0, '1k Out-of-Bounds Searches'); + i = 1000; len = 0; while (i > 0) { @@ -37,4 +38,4 @@ test('RTree Searching', function(t) { i--; } t.notEqual(len, 0, '1k In-Bounds Searches'); -}); \ No newline at end of file +}); From e6a0bed37e2d749a82f0fc03f7bee656da6d4b43 Mon Sep 17 00:00:00 2001 From: Will Forma Date: Mon, 29 Jan 2018 17:22:19 +0000 Subject: [PATCH 2/4] Added missing closing bracket and generated minified js file using uglify. --- dist/rtree.js | 1 + dist/rtree.min.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dist/rtree.js b/dist/rtree.js index e282b13..8493c21 100644 --- a/dist/rtree.js +++ b/dist/rtree.js @@ -334,6 +334,7 @@ Rectangle.overlapRectangle = function (a, b) { ((a.x + a.w) - b.x > -eps) && (a.y - (b.y + b.h) < eps) && ((a.y + a.h) - b.y > -eps); + } else { return a.x < (b.x + b.w) && (a.x + a.w) > b.x && a.y < (b.y + b.h) && (a.y + a.h) > b.y; } diff --git a/dist/rtree.min.js b/dist/rtree.min.js index c279f3b..4cbe75b 100644 --- a/dist/rtree.min.js +++ b/dist/rtree.min.js @@ -1 +1 @@ -!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.RTree=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;otemp.max[0]){temp.max[0]=a[i][0]}if(a[i][1]temp.max[1]){temp.max[1]=a[i][1]}i++}var out={x:temp.min[0],y:temp.min[1],w:temp.max[0]-temp.min[0],h:temp.max[1]-temp.min[1]};if(obj){out.leaf=obj}return out};var geoJSON={};geoJSON.point=function(obj,self){return self.insertSubtree({x:obj.geometry.coordinates[0],y:obj.geometry.coordinates[1],w:0,h:0,leaf:obj},self.root)};geoJSON.multiPointLineString=function(obj,self){return self.insertSubtree(bbox(obj.geometry.coordinates,obj),self.root)};geoJSON.multiLineStringPolygon=function(obj,self){return self.insertSubtree(bbox(Array.prototype.concat.apply([],obj.geometry.coordinates),obj),self.root)};geoJSON.multiPolygon=function(obj,self){return self.insertSubtree(bbox(Array.prototype.concat.apply([],Array.prototype.concat.apply([],obj.geometry.coordinates)),obj),self.root)};geoJSON.makeRec=function(obj){return rectangle(obj.x,obj.y,obj.w,obj.h)};geoJSON.geometryCollection=function(obj,self){if(obj.bbox){return self.insertSubtree({leaf:obj,x:obj.bbox[0],y:obj.bbox[1],w:obj.bbox[2]-obj.bbox[0],h:obj.bbox[3]-obj.bbox[1]},self.root)}var geos=obj.geometry.geometries;var i=0;var len=geos.length;var temp=[];var g;while(i=a.x()&&y<=a.y2()&&y2>=a.y()}return xa.x()&&ya.y()};this.expand=function(a){var nx,ny;var ax=a.x();var ay=a.y();var ax2=a.x2();var ay2=a.y2();if(x>ax){nx=ax}else{nx=x}if(y>ay){ny=ay}else{ny=y}if(x2>ax2){w=x2-nx}else{w=ax2-nx}if(y2>ay2){h=y2-ny}else{h=ay2-ny}x=nx;y=ny;return this}}Rectangle.overlapRectangle=function(a,b){if(a.h===0&&a.w===0||b.h===0&&b.w===0){return a.x<=b.x+b.w&&a.x+a.w>=b.x&&a.y<=b.y+b.h&&a.y+a.h>=b.y}else{return a.xb.x&&a.yb.y}};Rectangle.containsRectangle=function(a,b){return a.x+a.w<=b.x+b.w&&a.x>=b.x&&a.y+a.h<=b.y+b.h&&a.y>=b.y};Rectangle.expandRectangle=function(a,b){var nx,ny;var axw=a.x+a.w;var bxw=b.x+b.w;var ayh=a.y+a.h;var byh=b.y+b.h;if(a.x>b.x){nx=b.x}else{nx=a.x}if(a.y>b.y){ny=b.y}else{ny=a.y}if(axw>bxw){a.w=axw-nx}else{a.w=bxw-nx}if(ayh>byh){a.h=ayh-ny}else{a.h=byh-ny}a.x=nx;a.y=ny;return a};Rectangle.makeMBR=function(nodes,rect){if(!nodes.length){return{x:0,y:0,w:0,h:0}}rect=rect||{};rect.x=nodes[0].x;rect.y=nodes[0].y;rect.w=nodes[0].w;rect.h=nodes[0].h;for(var i=1,len=nodes.length;i0){tree=hitStack.pop();i=countStack.pop()-1;if("target"in retObj){while(i>=0){ltree=tree.nodes[i];if(rectangle.overlapRectangle(retObj,ltree)){if(retObj.target&&"leaf"in ltree&<ree.leaf===retObj.target||!retObj.target&&("leaf"in ltree||rectangle.containsRectangle(ltree,retObj))){if("nodes"in ltree){retArray=flatten(tree.nodes.splice(i,1))}else{retArray=tree.nodes.splice(i,1)}rectangle.makeMBR(tree.nodes,tree);delete retObj.target;break}else if("nodes"in ltree){currentDepth++;countStack.push(i);hitStack.push(tree);tree=ltree;i=ltree.nodes.length}}i--}}else if("nodes"in retObj){tree.nodes.splice(i+1,1);if(tree.nodes.length>0){rectangle.makeMBR(tree.nodes,tree)}for(var t=0;t0&&tree.nodes.length=0;i--){var ltree=nodes[i];if("leaf"in ltree){bestChoiceIndex=-1;break}var oldLRatio=rectangle.squarifiedRatio(ltree.w,ltree.h,ltree.nodes.length+1);var nw=Math.max(ltree.x+ltree.w,rect.x+rect.w)-Math.min(ltree.x,rect.x);var nh=Math.max(ltree.y+ltree.h,rect.y+rect.h)-Math.min(ltree.y,rect.y);var lratio=rectangle.squarifiedRatio(nw,nh,ltree.nodes.length+2);if(bestChoiceIndex<0||Math.abs(lratio-oldLRatio)0){pickNext(nodes,n[0],n[1])}return n};var pickNext=function(nodes,a,b){var areaA=rectangle.squarifiedRatio(a.w,a.h,a.nodes.length+1);var areaB=rectangle.squarifiedRatio(b.w,b.h,b.nodes.length+1);var highAreaDelta;var highAreaNode;var lowestGrowthGroup;for(var i=nodes.length-1;i>=0;i--){var l=nodes[i];var newAreaA={};newAreaA.x=Math.min(a.x,l.x);newAreaA.y=Math.min(a.y,l.y);newAreaA.w=Math.max(a.x+a.w,l.x+l.w)-newAreaA.x;newAreaA.h=Math.max(a.y+a.h,l.y+l.h)-newAreaA.y;var changeNewAreaA=Math.abs(rectangle.squarifiedRatio(newAreaA.w,newAreaA.h,a.nodes.length+2)-areaA);var newAreaB={};newAreaB.x=Math.min(b.x,l.x);newAreaB.y=Math.min(b.y,l.y);newAreaB.w=Math.max(b.x+b.w,l.x+l.w)-newAreaB.x;newAreaB.h=Math.max(b.y+b.h,l.y+l.h)-newAreaB.y;var changeNewAreaB=Math.abs(rectangle.squarifiedRatio(newAreaB.w,newAreaB.h,b.nodes.length+2)-areaB);if(!highAreaNode||!highAreaDelta||Math.abs(changeNewAreaB-changeNewAreaA)=0;i--){var l=nodes[i];if(l.x>nodes[highestLowX].x){highestLowX=i}else if(l.x+l.wnodes[highestLowY].y){highestLowY=i}else if(l.y+l.hdy){if(lowestHighX>highestLowX){t1=nodes.splice(lowestHighX,1)[0];t2=nodes.splice(highestLowX,1)[0]}else{t2=nodes.splice(highestLowX,1)[0];t1=nodes.splice(lowestHighX,1)[0]}}else{if(lowestHighY>highestLowY){t1=nodes.splice(lowestHighY,1)[0];t2=nodes.splice(highestLowY,1)[0]}else{t2=nodes.splice(highestLowY,1)[0];t1=nodes.splice(lowestHighY,1)[0]}}return[{x:t1.x,y:t1.y,w:t1.w,h:t1.h,nodes:[t1]},{x:t2.x,y:t2.y,w:t2.w,h:t2.h,nodes:[t2]}]};var attachData=function(node,moreTree){node.nodes=moreTree.nodes;node.x=moreTree.x;node.y=moreTree.y;node.w=moreTree.w;node.h=moreTree.h;return node};var searchSubtree=function(rect,returnNode,returnArray,root){var hitStack=[];if(!rectangle.overlapRectangle(rect,root)){return returnArray}hitStack.push(root.nodes);while(hitStack.length>0){var nodes=hitStack.pop();for(var i=nodes.length-1;i>=0;i--){var ltree=nodes[i];if(rectangle.overlapRectangle(rect,ltree)){if("nodes"in ltree){hitStack.push(ltree.nodes)}else if("leaf"in ltree){if(!returnNode){returnArray.push(ltree.leaf)}else{returnArray.push(ltree)}}}}}return returnArray};var insertSubtree=function(node,root){var bc;if(root.nodes.length===0){root.x=node.x;root.y=node.y;root.w=node.w;root.h=node.h;root.nodes.push(node);return}var treeStack=chooseLeafSubtree(node,root);var retObj=node;var pbc;while(treeStack.length>0){if(bc&&"nodes"in bc&&bc.nodes.length===0){pbc=bc;bc=treeStack.pop();for(var t=0;t0){deleted=removeSubtree(rect,false,rootTree);numberDeleted=deleted.length;retArray=retArray.concat(deleted)}return retArray};var removeObj=function(rect,obj){var retArray=removeSubtree(rect,obj,rootTree);return retArray};this.remove=function(rect,obj){if(!obj||typeof obj==="function"){return removeArea(rect,obj)}else{return removeObj(rect,obj)}};this.insert=function(rect,obj){var retArray=insertSubtree({x:rect.x,y:rect.y,w:rect.w,h:rect.h,leaf:obj},rootTree);return retArray}}RTree.prototype.toJSON=function(printing){return JSON.stringify(this.root,false,printing)};RTree.fromJSON=function(json){var rt=new RTree;rt.setTree(JSON.parse(json));return rt};module.exports=RTree;if(typeof Array.isArray!=="function"){Array.isArray=function(a){return typeof a==="object"&&{}.toString.call(a)==="[object Array]"}}},{"./rectangle":3}]},{},[2])(2)}); \ No newline at end of file +!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.RTree=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;otemp.max[0]){temp.max[0]=a[i][0]}if(a[i][1]temp.max[1]){temp.max[1]=a[i][1]}i++}var out={x:temp.min[0],y:temp.min[1],w:temp.max[0]-temp.min[0],h:temp.max[1]-temp.min[1]};if(obj){out.leaf=obj}return out};var geoJSON={};geoJSON.point=function(obj,self){return self.insertSubtree({x:obj.geometry.coordinates[0],y:obj.geometry.coordinates[1],w:0,h:0,leaf:obj},self.root)};geoJSON.multiPointLineString=function(obj,self){return self.insertSubtree(bbox(obj.geometry.coordinates,obj),self.root)};geoJSON.multiLineStringPolygon=function(obj,self){return self.insertSubtree(bbox(Array.prototype.concat.apply([],obj.geometry.coordinates),obj),self.root)};geoJSON.multiPolygon=function(obj,self){return self.insertSubtree(bbox(Array.prototype.concat.apply([],Array.prototype.concat.apply([],obj.geometry.coordinates)),obj),self.root)};geoJSON.makeRec=function(obj){return rectangle(obj.x,obj.y,obj.w,obj.h)};geoJSON.geometryCollection=function(obj,self){if(obj.bbox){return self.insertSubtree({leaf:obj,x:obj.bbox[0],y:obj.bbox[1],w:obj.bbox[2]-obj.bbox[0],h:obj.bbox[3]-obj.bbox[1]},self.root)}var geos=obj.geometry.geometries;var i=0;var len=geos.length;var temp=[];var g;while(i=a.x()&&y<=a.y2()&&y2>=a.y()}return xa.x()&&ya.y()};this.expand=function(a){var nx,ny;var ax=a.x();var ay=a.y();var ax2=a.x2();var ay2=a.y2();if(x>ax){nx=ax}else{nx=x}if(y>ay){ny=ay}else{ny=y}if(x2>ax2){w=x2-nx}else{w=ax2-nx}if(y2>ay2){h=y2-ny}else{h=ay2-ny}x=nx;y=ny;return this}}Rectangle.overlapRectangle=function(a,b){var eps=1e-6;if(a.h-eps&&a.y-(b.y+b.h)-eps}else{return a.xb.x&&a.yb.y}};Rectangle.containsRectangle=function(a,b){return a.x+a.w<=b.x+b.w&&a.x>=b.x&&a.y+a.h<=b.y+b.h&&a.y>=b.y};Rectangle.expandRectangle=function(a,b){var nx,ny;var axw=a.x+a.w;var bxw=b.x+b.w;var ayh=a.y+a.h;var byh=b.y+b.h;if(a.x>b.x){nx=b.x}else{nx=a.x}if(a.y>b.y){ny=b.y}else{ny=a.y}if(axw>bxw){a.w=axw-nx}else{a.w=bxw-nx}if(ayh>byh){a.h=ayh-ny}else{a.h=byh-ny}a.x=nx;a.y=ny;return a};Rectangle.makeMBR=function(nodes,rect){if(!nodes.length){return{x:0,y:0,w:0,h:0}}rect=rect||{};rect.x=nodes[0].x;rect.y=nodes[0].y;rect.w=nodes[0].w;rect.h=nodes[0].h;for(var i=1,len=nodes.length;i0){tree=hitStack.pop();i=countStack.pop()-1;if("target"in retObj){while(i>=0){ltree=tree.nodes[i];if(rectangle.overlapRectangle(retObj,ltree)){if(retObj.target&&"leaf"in ltree&<ree.leaf===retObj.target||!retObj.target&&("leaf"in ltree||rectangle.containsRectangle(ltree,retObj))){if("nodes"in ltree){retArray=flatten(tree.nodes.splice(i,1))}else{retArray=tree.nodes.splice(i,1)}rectangle.makeMBR(tree.nodes,tree);delete retObj.target;break}else if("nodes"in ltree){currentDepth++;countStack.push(i);hitStack.push(tree);tree=ltree;i=ltree.nodes.length}}i--}}else if("nodes"in retObj){tree.nodes.splice(i+1,1);if(tree.nodes.length>0){rectangle.makeMBR(tree.nodes,tree)}for(var t=0;t0&&tree.nodes.length=0;i--){var ltree=nodes[i];if("leaf"in ltree){bestChoiceIndex=-1;break}var oldLRatio=rectangle.squarifiedRatio(ltree.w,ltree.h,ltree.nodes.length+1);var nw=Math.max(ltree.x+ltree.w,rect.x+rect.w)-Math.min(ltree.x,rect.x);var nh=Math.max(ltree.y+ltree.h,rect.y+rect.h)-Math.min(ltree.y,rect.y);var lratio=rectangle.squarifiedRatio(nw,nh,ltree.nodes.length+2);if(bestChoiceIndex<0||Math.abs(lratio-oldLRatio)0){pickNext(nodes,n[0],n[1])}return n};var pickNext=function(nodes,a,b){var areaA=rectangle.squarifiedRatio(a.w,a.h,a.nodes.length+1);var areaB=rectangle.squarifiedRatio(b.w,b.h,b.nodes.length+1);var highAreaDelta;var highAreaNode;var lowestGrowthGroup;for(var i=nodes.length-1;i>=0;i--){var l=nodes[i];var newAreaA={};newAreaA.x=Math.min(a.x,l.x);newAreaA.y=Math.min(a.y,l.y);newAreaA.w=Math.max(a.x+a.w,l.x+l.w)-newAreaA.x;newAreaA.h=Math.max(a.y+a.h,l.y+l.h)-newAreaA.y;var changeNewAreaA=Math.abs(rectangle.squarifiedRatio(newAreaA.w,newAreaA.h,a.nodes.length+2)-areaA);var newAreaB={};newAreaB.x=Math.min(b.x,l.x);newAreaB.y=Math.min(b.y,l.y);newAreaB.w=Math.max(b.x+b.w,l.x+l.w)-newAreaB.x;newAreaB.h=Math.max(b.y+b.h,l.y+l.h)-newAreaB.y;var changeNewAreaB=Math.abs(rectangle.squarifiedRatio(newAreaB.w,newAreaB.h,b.nodes.length+2)-areaB);if(!highAreaNode||!highAreaDelta||Math.abs(changeNewAreaB-changeNewAreaA)=0;i--){var l=nodes[i];if(l.x>nodes[highestLowX].x){highestLowX=i}else if(l.x+l.wnodes[highestLowY].y){highestLowY=i}else if(l.y+l.hdy){if(lowestHighX>highestLowX){t1=nodes.splice(lowestHighX,1)[0];t2=nodes.splice(highestLowX,1)[0]}else{t2=nodes.splice(highestLowX,1)[0];t1=nodes.splice(lowestHighX,1)[0]}}else{if(lowestHighY>highestLowY){t1=nodes.splice(lowestHighY,1)[0];t2=nodes.splice(highestLowY,1)[0]}else{t2=nodes.splice(highestLowY,1)[0];t1=nodes.splice(lowestHighY,1)[0]}}return[{x:t1.x,y:t1.y,w:t1.w,h:t1.h,nodes:[t1]},{x:t2.x,y:t2.y,w:t2.w,h:t2.h,nodes:[t2]}]};var attachData=function(node,moreTree){node.nodes=moreTree.nodes;node.x=moreTree.x;node.y=moreTree.y;node.w=moreTree.w;node.h=moreTree.h;return node};var searchSubtree=function(rect,returnNode,returnArray,root){var hitStack=[];if(!rectangle.overlapRectangle(rect,root)){return returnArray}hitStack.push(root.nodes);while(hitStack.length>0){var nodes=hitStack.pop();for(var i=nodes.length-1;i>=0;i--){var ltree=nodes[i];if(rectangle.overlapRectangle(rect,ltree)){if("nodes"in ltree){hitStack.push(ltree.nodes)}else if("leaf"in ltree){if(!returnNode){returnArray.push(ltree.leaf)}else{returnArray.push(ltree)}}}}}return returnArray};var insertSubtree=function(node,root){var bc;if(root.nodes.length===0){root.x=node.x;root.y=node.y;root.w=node.w;root.h=node.h;root.nodes.push(node);return}var treeStack=chooseLeafSubtree(node,root);var retObj=node;var pbc;while(treeStack.length>0){if(bc&&"nodes"in bc&&bc.nodes.length===0){pbc=bc;bc=treeStack.pop();for(var t=0;t0){deleted=removeSubtree(rect,false,rootTree);numberDeleted=deleted.length;retArray=retArray.concat(deleted)}return retArray};var removeObj=function(rect,obj){var retArray=removeSubtree(rect,obj,rootTree);return retArray};this.remove=function(rect,obj){if(!obj||typeof obj==="function"){return removeArea(rect,obj)}else{return removeObj(rect,obj)}};this.insert=function(rect,obj){var retArray=insertSubtree({x:rect.x,y:rect.y,w:rect.w,h:rect.h,leaf:obj},rootTree);return retArray}}RTree.prototype.toJSON=function(printing){return JSON.stringify(this.root,false,printing)};RTree.fromJSON=function(json){var rt=new RTree;rt.setTree(JSON.parse(json));return rt};module.exports=RTree;if(typeof Array.isArray!=="function"){Array.isArray=function(a){return typeof a==="object"&&{}.toString.call(a)==="[object Array]"}}},{"./rectangle":3}]},{},[2])(2)}); \ No newline at end of file From af6a1704c18592a84f9c11b116fe62e176512561 Mon Sep 17 00:00:00 2001 From: Will Forma Date: Mon, 29 Jan 2018 17:36:33 +0000 Subject: [PATCH 3/4] Added missing semicolon to make TravisCI happy --- lib/rectangle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rectangle.js b/lib/rectangle.js index 2d1c984..0304696 100644 --- a/lib/rectangle.js +++ b/lib/rectangle.js @@ -104,7 +104,7 @@ function Rectangle(x, y, w, h) { // new Rectangle(bounds) or new Rectangle(x, y, */ Rectangle.overlapRectangle = function (a, b) { //if(!((a.h||a.w)&&(b.h||b.w))){ not faster resist the urge! - var eps = 1e-6 + var eps = 1e-6; if ((a.h < eps && a.w < eps) || (b.h < eps && b.w < eps)) { return (a.x - (b.x + b.w) < eps) && ((a.x + a.w) - b.x > -eps) && From c2101602bf23b956f1e1a6ce7fc8d6d172ca92cf Mon Sep 17 00:00:00 2001 From: Will Forma Date: Mon, 29 Jan 2018 17:48:52 +0000 Subject: [PATCH 4/4] Reorganized the code to hopefully appease the bot overlords. Out of scope for this PR, but it is what it is. --- lib/rtree.js | 281 ++++++++++++++++++++++++++------------------------- 1 file changed, 141 insertions(+), 140 deletions(-) diff --git a/lib/rtree.js b/lib/rtree.js index 3eb0144..5fe9636 100644 --- a/lib/rtree.js +++ b/lib/rtree.js @@ -33,146 +33,7 @@ function RTree(width) { } return done; }; - /* find the best specific node(s) for object to be deleted from - * [ leaf node parent ] = removeSubtree(rectangle, object, root) - * @private - */ - var removeSubtree = function (rect, obj, root) { - var hitStack = []; // Contains the elements that overlap - var countStack = []; // Contains the elements that overlap - var retArray = []; - var currentDepth = 1; - var tree, i, ltree; - if (!rect || !rectangle.overlapRectangle(rect, root)) { - return retArray; - } - var retObj = {x: rect.x, y: rect.y, w: rect.w, h: rect.h, target: obj}; - - countStack.push(root.nodes.length); - hitStack.push(root); - while (hitStack.length > 0) { - tree = hitStack.pop(); - i = countStack.pop() - 1; - if ('target' in retObj) { // will this ever be false? - while (i >= 0) { - ltree = tree.nodes[i]; - if (rectangle.overlapRectangle(retObj, ltree)) { - if ((retObj.target && 'leaf' in ltree && ltree.leaf === retObj.target) || (!retObj.target && ('leaf' in ltree || rectangle.containsRectangle(ltree, retObj)))) { - // A Match !! - // Yup we found a match... - // we can cancel search and start walking up the list - if ('nodes' in ltree) {// If we are deleting a node not a leaf... - retArray = flatten(tree.nodes.splice(i, 1)); - } else { - retArray = tree.nodes.splice(i, 1); - } - // Resize MBR down... - rectangle.makeMBR(tree.nodes, tree); - delete retObj.target; - //if (tree.nodes.length < minWidth) { // Underflow - // retObj.nodes = searchSubtree(tree, true, [], tree); - //} - break; - } else if ('nodes' in ltree) { // Not a Leaf - currentDepth++; - countStack.push(i); - hitStack.push(tree); - tree = ltree; - i = ltree.nodes.length; - } - } - i--; - } - - } else if ('nodes' in retObj) { // We are unsplitting - - tree.nodes.splice(i + 1, 1); // Remove unsplit node - if (tree.nodes.length > 0) { - rectangle.makeMBR(tree.nodes, tree); - } - for (var t = 0;t < retObj.nodes.length;t++) { - insertSubtree(retObj.nodes[t], tree); - } - retObj.nodes = []; - if (hitStack.length === 0 && tree.nodes.length <= 1) { // Underflow..on root! - retObj.nodes = searchSubtree(tree, true, retObj.nodes, tree); - tree.nodes = []; - hitStack.push(tree); - countStack.push(1); - } else if (hitStack.length > 0 && tree.nodes.length < minWidth) { // Underflow..AGAIN! - retObj.nodes = searchSubtree(tree, true, retObj.nodes, tree); - tree.nodes = []; - } else { - delete retObj.nodes; // Just start resizing - } - } else { // we are just resizing - rectangle.makeMBR(tree.nodes, tree); - } - currentDepth -= 1; - } - return retArray; - }; - - /* choose the best damn node for rectangle to be inserted into - * [ leaf node parent ] = chooseLeafSubtree(rectangle, root to start search at) - * @private - */ - var chooseLeafSubtree = function (rect, root) { - var bestChoiceIndex = -1; - var bestChoiceStack = []; - var bestChoiceArea; - var first = true; - bestChoiceStack.push(root); - var nodes = root.nodes; - - while (first || bestChoiceIndex !== -1) { - if (first) { - first = false; - } else { - bestChoiceStack.push(nodes[bestChoiceIndex]); - nodes = nodes[bestChoiceIndex].nodes; - bestChoiceIndex = -1; - } - - for (var i = nodes.length - 1; i >= 0; i--) { - var ltree = nodes[i]; - if ('leaf' in ltree) { - // Bail out of everything and start inserting - bestChoiceIndex = -1; - break; - } - // Area of new enlarged rectangle - var oldLRatio = rectangle.squarifiedRatio(ltree.w, ltree.h, ltree.nodes.length + 1); - - // Enlarge rectangle to fit new rectangle - var nw = Math.max(ltree.x + ltree.w, rect.x + rect.w) - Math.min(ltree.x, rect.x); - var nh = Math.max(ltree.y + ltree.h, rect.y + rect.h) - Math.min(ltree.y, rect.y); - - // Area of new enlarged rectangle - var lratio = rectangle.squarifiedRatio(nw, nh, ltree.nodes.length + 2); - - if (bestChoiceIndex < 0 || Math.abs(lratio - oldLRatio) < bestChoiceArea) { - bestChoiceArea = Math.abs(lratio - oldLRatio); - bestChoiceIndex = i; - } - } - } - - return bestChoiceStack; - }; - - /* split a set of nodes into two roughly equally-filled nodes - * [ an array of two new arrays of nodes ] = linearSplit(array of nodes) - * @private - */ - var linearSplit = function (nodes) { - var n = pickLinear(nodes); - while (nodes.length > 0) { - pickNext(nodes, n[0], n[1]); - } - return n; - }; - + /* insert the best source rectangle into the best fitting parent node: a or b * [] = pickNext(array of source nodes, target node array a, target node array b) * @private @@ -391,6 +252,146 @@ function RTree(width) { } }; + /* find the best specific node(s) for object to be deleted from + * [ leaf node parent ] = removeSubtree(rectangle, object, root) + * @private + */ + var removeSubtree = function (rect, obj, root) { + var hitStack = []; // Contains the elements that overlap + var countStack = []; // Contains the elements that overlap + var retArray = []; + var currentDepth = 1; + var tree, i, ltree; + if (!rect || !rectangle.overlapRectangle(rect, root)) { + return retArray; + } + var retObj = {x: rect.x, y: rect.y, w: rect.w, h: rect.h, target: obj}; + + countStack.push(root.nodes.length); + hitStack.push(root); + while (hitStack.length > 0) { + tree = hitStack.pop(); + i = countStack.pop() - 1; + if ('target' in retObj) { // will this ever be false? + while (i >= 0) { + ltree = tree.nodes[i]; + if (rectangle.overlapRectangle(retObj, ltree)) { + if ((retObj.target && 'leaf' in ltree && ltree.leaf === retObj.target) || (!retObj.target && ('leaf' in ltree || rectangle.containsRectangle(ltree, retObj)))) { + // A Match !! + // Yup we found a match... + // we can cancel search and start walking up the list + if ('nodes' in ltree) {// If we are deleting a node not a leaf... + retArray = flatten(tree.nodes.splice(i, 1)); + } else { + retArray = tree.nodes.splice(i, 1); + } + // Resize MBR down... + rectangle.makeMBR(tree.nodes, tree); + delete retObj.target; + //if (tree.nodes.length < minWidth) { // Underflow + // retObj.nodes = searchSubtree(tree, true, [], tree); + //} + break; + } else if ('nodes' in ltree) { // Not a Leaf + currentDepth++; + countStack.push(i); + hitStack.push(tree); + tree = ltree; + i = ltree.nodes.length; + } + } + i--; + } + + } else if ('nodes' in retObj) { // We are unsplitting + + tree.nodes.splice(i + 1, 1); // Remove unsplit node + if (tree.nodes.length > 0) { + rectangle.makeMBR(tree.nodes, tree); + } + for (var t = 0;t < retObj.nodes.length;t++) { + insertSubtree(retObj.nodes[t], tree); + } + retObj.nodes = []; + if (hitStack.length === 0 && tree.nodes.length <= 1) { // Underflow..on root! + retObj.nodes = searchSubtree(tree, true, retObj.nodes, tree); + tree.nodes = []; + hitStack.push(tree); + countStack.push(1); + } else if (hitStack.length > 0 && tree.nodes.length < minWidth) { // Underflow..AGAIN! + retObj.nodes = searchSubtree(tree, true, retObj.nodes, tree); + tree.nodes = []; + } else { + delete retObj.nodes; // Just start resizing + } + } else { // we are just resizing + rectangle.makeMBR(tree.nodes, tree); + } + currentDepth -= 1; + } + return retArray; + }; + + /* choose the best damn node for rectangle to be inserted into + * [ leaf node parent ] = chooseLeafSubtree(rectangle, root to start search at) + * @private + */ + var chooseLeafSubtree = function (rect, root) { + var bestChoiceIndex = -1; + var bestChoiceStack = []; + var bestChoiceArea; + var first = true; + bestChoiceStack.push(root); + var nodes = root.nodes; + + while (first || bestChoiceIndex !== -1) { + if (first) { + first = false; + } else { + bestChoiceStack.push(nodes[bestChoiceIndex]); + nodes = nodes[bestChoiceIndex].nodes; + bestChoiceIndex = -1; + } + + for (var i = nodes.length - 1; i >= 0; i--) { + var ltree = nodes[i]; + if ('leaf' in ltree) { + // Bail out of everything and start inserting + bestChoiceIndex = -1; + break; + } + // Area of new enlarged rectangle + var oldLRatio = rectangle.squarifiedRatio(ltree.w, ltree.h, ltree.nodes.length + 1); + + // Enlarge rectangle to fit new rectangle + var nw = Math.max(ltree.x + ltree.w, rect.x + rect.w) - Math.min(ltree.x, rect.x); + var nh = Math.max(ltree.y + ltree.h, rect.y + rect.h) - Math.min(ltree.y, rect.y); + + // Area of new enlarged rectangle + var lratio = rectangle.squarifiedRatio(nw, nh, ltree.nodes.length + 2); + + if (bestChoiceIndex < 0 || Math.abs(lratio - oldLRatio) < bestChoiceArea) { + bestChoiceArea = Math.abs(lratio - oldLRatio); + bestChoiceIndex = i; + } + } + } + + return bestChoiceStack; + }; + + /* split a set of nodes into two roughly equally-filled nodes + * [ an array of two new arrays of nodes ] = linearSplit(array of nodes) + * @private + */ + var linearSplit = function (nodes) { + var n = pickLinear(nodes); + while (nodes.length > 0) { + pickNext(nodes, n[0], n[1]); + } + return n; + }; + this.insertSubtree = insertSubtree; /* quick 'n' dirty function for plugins or manually drawing the tree * [ tree ] = RTree.getTree(): returns the raw tree data. useful for adding