diff geotemco/js/Map/Binning.js @ 0:57bde4830927

first commit
author Zoe Hong <zhong@mpiwg-berlin.mpg.de>
date Tue, 24 Mar 2015 11:37:17 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/geotemco/js/Map/Binning.js	Tue Mar 24 11:37:17 2015 +0100
@@ -0,0 +1,749 @@
+/*
+* Binning.js
+*
+* Copyright (c) 2012, Stefan Jänicke. All rights reserved.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+* MA 02110-1301  USA
+*/
+
+/**
+ * @class Binning
+ * Calculates map aggregation with several binning algorithms
+ * @author Stefan Jänicke (stjaenicke@informatik.uni-leipzig.de)
+ * @release 1.0
+ * @release date: 2012-07-27
+ * @version date: 2012-07-27
+ */
+Binning = function(map, options) {
+
+	this.map = map;
+	this.options = options;
+	this.reset();
+
+};
+
+Binning.prototype = {
+
+	getSet : function() {
+		var type = this.options.binning;
+		if (!type) {
+			return this.getExactBinning();
+		} else if (type == 'generic') {
+			return this.getGenericBinning();
+		} else if (type == 'square') {
+			return this.getSquareBinning();
+		} else if (type == 'hexagonal') {
+			return this.getHexagonalBinning();
+		} else if (type == 'triangular') {
+			return this.getTriangularBinning();
+		}
+	},
+
+	getExactBinning : function() {
+		if ( typeof this.binnings['exact'] == 'undefined') {
+			this.exactBinning();
+		}
+		return this.binnings['exact'];
+	},
+
+	getGenericBinning : function() {
+		if ( typeof this.binnings['generic'] == 'undefined') {
+			this.genericBinning();
+		}
+		return this.binnings['generic'];
+	},
+
+	getSquareBinning : function() {
+		if ( typeof this.binnings['square'] == 'undefined') {
+			this.squareBinning();
+		}
+		return this.binnings['square'];
+	},
+
+	getHexagonalBinning : function() {
+		if ( typeof this.binnings['hexagonal'] == 'undefined') {
+			this.hexagonalBinning();
+		}
+		return this.binnings['hexagonal'];
+	},
+
+	getTriangularBinning : function() {
+		if ( typeof this.binnings['triangular'] == 'undefined') {
+			this.triangularBinning();
+		}
+		return this.binnings['triangular'];
+	},
+
+	reset : function() {
+		this.zoomLevels = this.map.getNumZoomLevels();
+		this.binnings = [];
+		this.minimumRadius = this.options.minimumRadius;
+		this.maximumRadius = this.minimumRadius;
+		this.maximumPoints = 0;
+		this.minArea = 0;
+		this.maxArea = 0;
+	},
+
+	getMaxRadius : function(size) {
+		return 4 * Math.log(size) / Math.log(2);
+	},
+
+	setObjects : function(objects) {
+		this.objects = objects;
+		for (var i = 0; i < this.objects.length; i++) {
+			var weight = 0;
+			for (var j = 0; j < this.objects[i].length; j++) {
+				if (this.objects[i][j].isGeospatial) {
+					weight += this.objects[i][j].weight;
+				}
+			}
+			var r = this.getMaxRadius(weight);
+			if (r > this.maximumRadius) {
+				this.maximumRadius = r;
+				this.maximumPoints = weight;
+				this.maxArea = Math.PI * this.maximumRadius * this.maximumRadius;
+				this.minArea = Math.PI * this.minimumRadius * this.minimumRadius;
+			}
+		}
+	},
+
+	dist : function(x1, y1, x2, y2) {
+		return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
+	},
+
+	exactBinning : function() {
+		var circleSets = [];
+		var hashMaps = [];
+		var selectionHashs = [];
+
+		var circleAggregates = [];
+		var bins = [];
+		for (var i = 0; i < this.objects.length; i++) {
+			bins.push([]);
+			circleAggregates.push([]);
+			for (var j = 0; j < this.objects[i].length; j++) {
+				var o = this.objects[i][j];
+				if (o.isGeospatial) {
+					if ( typeof circleAggregates[i]['' + o.getLongitude(this.options.mapIndex)] == 'undefined') {
+						circleAggregates[i]['' + o.getLongitude(this.options.mapIndex)] = [];
+					}
+					if ( typeof circleAggregates[i][''+o.getLongitude(this.options.mapIndex)]['' + o.getLatitude(this.options.mapIndex)] == 'undefined') {
+						circleAggregates[i][''+o.getLongitude(this.options.mapIndex)]['' + o.getLatitude(this.options.mapIndex)] = [];
+						bins[i].push(circleAggregates[i][''+o.getLongitude(this.options.mapIndex)]['' + o.getLatitude(this.options.mapIndex)]);
+					}
+					circleAggregates[i][''+o.getLongitude(this.options.mapIndex)]['' + o.getLatitude(this.options.mapIndex)].push(o);
+				}
+			}
+		}
+
+		var circles = [];
+		var hashMap = [];
+		var selectionMap = [];
+		for (var i = 0; i < bins.length; i++) {
+			circles.push([]);
+			hashMap.push([]);
+			selectionMap.push([]);
+			for (var j = 0; j < bins[i].length; j++) {
+				var bin = bins[i][j];
+				var p = new OpenLayers.Geometry.Point(bin[0].getLongitude(this.options.mapIndex), bin[0].getLatitude(this.options.mapIndex), null);
+				p.transform(this.map.displayProjection, this.map.projection);
+				var weight = 0;
+				for (var z = 0; z < bin.length; z++) {
+					weight += bin[z].weight;
+				}
+				var radius = this.options.minimumRadius;
+				if (this.options.noBinningRadii == 'dynamic') {
+					radius = this.getRadius(weight);
+				}
+				var circle = new CircleObject(p.x, p.y, 0, 0, bin, radius, i, weight);
+				circles[i].push(circle);
+				for (var z = 0; z < bin.length; z++) {
+					hashMap[i][bin[z].index] = circle;
+					selectionMap[i][bin[z].index] = false;
+				}
+			}
+		}
+		for (var k = 0; k < this.zoomLevels; k++) {
+			circleSets.push(circles);
+			hashMaps.push(hashMap);
+			selectionHashs.push(selectionMap);
+		}
+		this.binnings['exact'] = {
+			circleSets : circleSets,
+			hashMaps : hashMaps,
+			selectionHashs : selectionHashs
+		};
+	},
+
+	genericClustering : function(objects, id) {
+		var binSets = [];
+		var circleSets = [];
+		var hashMaps = [];
+		var selectionHashs = [];
+		var clustering = new Clustering(-20037508.34, -20037508.34, 20037508.34, 20037508.34);
+		for (var i = 0; i < objects.length; i++) {
+			for (var j = 0; j < objects[i].length; j++) {
+				var o = objects[i][j];
+				if (o.isGeospatial) {
+					var p = new OpenLayers.Geometry.Point(o.getLongitude(this.options.mapIndex), o.getLatitude(this.options.mapIndex), null);
+					p.transform(this.map.displayProjection, this.map.projection);
+					var point = new Vertex(Math.floor(p.x), Math.floor(p.y), objects.length, this);
+					point.addElement(o, o.weight, i);
+					clustering.add(point);
+				}
+			}
+		}
+
+		for (var i = 0; i < this.zoomLevels; i++) {
+			var bins = [];
+			var circles = [];
+			var hashMap = [];
+			var selectionMap = [];
+			for (var j = 0; j < objects.length; j++) {
+				circles.push([]);
+				hashMap.push([]);
+				selectionMap.push([]);
+			}
+			var resolution = this.map.getResolutionForZoom(this.zoomLevels - i - 1);
+			clustering.mergeForResolution(resolution, this.options.circleGap, this.options.circleOverlap);
+			for (var j = 0; j < clustering.vertices.length; j++) {
+				var point = clustering.vertices[j];
+				if (!point.legal) {
+					continue;
+				}
+				var balls = [];
+				for (var k = 0; k < point.elements.length; k++) {
+					if (point.elements[k].length > 0) {
+						balls.push({
+							search : k,
+							elements : point.elements[k],
+							radius : point.radii[k],
+							weight : point.weights[k]
+						});
+					}
+				}
+				var orderBalls = function(b1, b2) {
+					if (b1.radius > b2.radius) {
+						return -1;
+					}
+					if (b2.radius > b1.radius) {
+						return 1;
+					}
+					return 0;
+				}
+				var fatherBin = {
+					circles : [],
+					length : 0,
+					radius : point.radius / resolution,
+					x : point.x,
+					y : point.y
+				};
+				for (var k = 0; k < objects.length; k++) {
+					fatherBin.circles.push(false);
+				}
+				var createCircle = function(sx, sy, ball) {
+					var index = id || ball.search;
+					var circle = new CircleObject(point.x, point.y, sx, sy, ball.elements, ball.radius, index, ball.weight, fatherBin);
+					circles[ball.search].push(circle);
+					fatherBin.circles[index] = circle;
+					fatherBin.length++;
+					for (var k = 0; k < ball.elements.length; k++) {
+						hashMap[ball.search][ball.elements[k].index] = circle;
+						selectionMap[ball.search][ball.elements[k].index] = false;
+					}
+				}
+				if (balls.length == 1) {
+					createCircle(0, 0, balls[0]);
+				} else if (balls.length == 2) {
+					var r1 = balls[0].radius;
+					var r2 = balls[1].radius;
+					createCircle(-1 * r2, 0, balls[0]);
+					createCircle(r1, 0, balls[1]);
+				} else if (balls.length == 3) {
+					balls.sort(orderBalls);
+					var r1 = balls[0].radius;
+					var r2 = balls[1].radius;
+					var r3 = balls[2].radius;
+					var d = ((2 / 3 * Math.sqrt(3) - 1) / 2) * r2;
+					var delta1 = point.radius / resolution - r1 - d;
+					var delta2 = r1 - delta1;
+					createCircle(-delta1, 0, balls[0]);
+					createCircle(delta2 + r2 - 3 * d, r2, balls[1]);
+					createCircle(delta2 + r2 - 3 * d, -1 * r3, balls[2]);
+//					createCircle(delta2 + r3 - (3 * d * r3 / r2), -1 * r3, balls[2]);
+				} else if (balls.length == 4) {
+					balls.sort(orderBalls);
+					var r1 = balls[0].radius;
+					var r2 = balls[1].radius;
+					var r3 = balls[2].radius;
+					var r4 = balls[3].radius;
+					var d = (Math.sqrt(2) - 1) * r2;
+					createCircle(-1 * d - r2, 0, balls[0]);
+					createCircle(r1 - r2, -1 * d - r4, balls[3]);
+					createCircle(r1 - r2, d + r3, balls[2]);
+					createCircle(d + r1, 0, balls[1]);
+				}
+				if (fatherBin.length > 1) {
+					bins.push(fatherBin);
+				}
+			}
+			circleSets.push(circles);
+			binSets.push(bins);
+			hashMaps.push(hashMap);
+			selectionHashs.push(selectionMap);
+		}
+		circleSets.reverse();
+		binSets.reverse();
+		hashMaps.reverse();
+		selectionHashs.reverse();
+		return {
+			circleSets : circleSets,
+			binSets : binSets,
+			hashMaps : hashMaps,
+			selectionHashs : selectionHashs
+		};
+	},
+
+	genericBinning : function() {
+		if (this.options.circlePackings || this.objects.length == 1) {
+			this.binnings['generic'] = this.genericClustering(this.objects);
+		} else {
+			var circleSets = [];
+			var hashMaps = [];
+			var selectionHashs = [];
+			for (var i = 0; i < this.objects.length; i++) {
+				var sets = this.genericClustering([this.objects[i]], i);
+				if (i == 0) {
+					circleSets = sets.circleSets;
+					hashMaps = sets.hashMaps;
+					selectionHashs = sets.selectionHashs;
+				} else {
+					for (var j = 0; j < circleSets.length; j++) {
+						circleSets[j] = circleSets[j].concat(sets.circleSets[j]);
+						hashMaps[j] = hashMaps[j].concat(sets.hashMaps[j]);
+						selectionHashs[j] = selectionHashs[j].concat(sets.selectionHashs[j]);
+					}
+				}
+			}
+			this.binnings['generic'] = {
+				circleSets : circleSets,
+				hashMaps : hashMaps,
+				selectionHashs : selectionHashs
+			};
+		}
+	},
+
+	getRadius : function(n) {
+		if (n == 0) {
+			return 0;
+		}
+		if (n == 1) {
+			return this.minimumRadius;
+		}
+		return Math.sqrt((this.minArea + (this.maxArea - this.minArea) / (this.maximumPoints - 1) * (n - 1) ) / Math.PI);
+	},
+
+	getBinRadius : function(n, r_max, N) {
+		if (n == 0) {
+			return 0;
+		}
+		/*
+		function log2(x) {
+			return (Math.log(x)) / (Math.log(2));
+		}
+		var r0 = this.options.minimumRadius;
+		var r;
+		if ( typeof r_max == 'undefined') {
+			return r0 + n / Math.sqrt(this.options.maximumPoints);
+		}
+		return r0 + (r_max - r0 ) * log2(n) / log2(N);
+		*/
+		var minArea = Math.PI * this.options.minimumRadius * this.options.minimumRadius;
+		var maxArea = Math.PI * r_max * r_max;
+		return Math.sqrt((minArea + (maxArea - minArea) / (N - 1) * (n - 1) ) / Math.PI);
+	},
+
+	shift : function(type, bin, radius, elements) {
+
+		var x1 = bin.x, x2 = 0;
+		var y1 = bin.y, y2 = 0;
+		for (var i = 0; i < elements.length; i++) {
+			x2 += elements[i].x / elements.length;
+			y2 += elements[i].y / elements.length;
+		}
+
+		var sx = 0, sy = 0;
+
+		if (type == 'square') {
+			var dx = Math.abs(x2 - x1);
+			var dy = Math.abs(y2 - y1);
+			var m = dy / dx;
+			var n = y1 - m * x1;
+			if (dx > dy) {
+				sx = bin.x - (x1 + bin.r - radius );
+				sy = bin.y - (m * bin.x + n );
+			} else {
+				sy = bin.y - (y1 + bin.r - radius );
+				sx = bin.x - (bin.y - n) / m;
+			}
+		}
+
+		return {
+			x : sx,
+			y : sy
+		};
+
+	},
+
+	binSize : function(elements) {
+		var size = 0;
+		for (var i in elements ) {
+			size += elements[i].weight;
+		}
+		return size;
+	},
+
+	setCircleSet : function(id, binData) {
+		var circleSets = [];
+		var hashMaps = [];
+		var selectionHashs = [];
+		for (var i = 0; i < binData.length; i++) {
+			var circles = [];
+			var hashMap = [];
+			var selectionMap = [];
+			for (var j = 0; j < this.objects.length; j++) {
+				circles.push([]);
+				hashMap.push([]);
+				selectionMap.push([]);
+			}
+			var points = [];
+			var max = 0;
+			var radius = 0;
+			var resolution = this.map.getResolutionForZoom(i);
+			for (var j = 0; j < binData[i].length; j++) {
+				for (var k = 0; k < binData[i][j].bin.length; k++) {
+					var bs = this.binSize(binData[i][j].bin[k]);
+					if (bs > max) {
+						max = bs;
+						radius = binData[i][j].r / resolution;
+					}
+				}
+			}
+			for (var j = 0; j < binData[i].length; j++) {
+				var bin = binData[i][j];
+				for (var k = 0; k < bin.bin.length; k++) {
+					if (bin.bin[k].length == 0) {
+						continue;
+					}
+					var weight = this.binSize(bin.bin[k]);
+					var r = this.getBinRadius(weight, radius, max);
+					var shift = this.shift(id, bin, r * resolution, bin.bin[k], i);
+					var circle = new CircleObject(bin.x - shift.x, bin.y - shift.y, 0, 0, bin.bin[k], r, k, weight);
+					circles[k].push(circle);
+					for (var z = 0; z < bin.bin[k].length; z++) {
+						hashMap[k][bin.bin[k][z].index] = circle;
+						selectionMap[k][bin.bin[k][z].index] = false;
+					}
+				}
+			}
+			circleSets.push(circles);
+			hashMaps.push(hashMap);
+			selectionHashs.push(selectionMap);
+		}
+		this.binnings[id] = {
+			circleSets : circleSets,
+			hashMaps : hashMaps,
+			selectionHashs : selectionHashs
+		};
+	},
+
+	squareBinning : function() {
+
+		var l = 20037508.34;
+		var area0 = l * l * 4;
+		var binCount = this.options.binCount;
+
+		var bins = [];
+		var binData = [];
+		for (var k = 0; k < this.zoomLevels; k++) {
+			bins.push([]);
+			binData.push([]);
+		}
+
+		for (var i = 0; i < this.objects.length; i++) {
+			for (var j = 0; j < this.objects[i].length; j++) {
+				var o = this.objects[i][j];
+				if (!o.isGeospatial) {
+					continue;
+				}
+				var p = new OpenLayers.Geometry.Point(o.getLongitude(this.options.mapIndex), o.getLatitude(this.options.mapIndex), null);
+				p.transform(this.map.displayProjection, this.map.projection);
+				o.x = p.x;
+				o.y = p.y;
+				for (var k = 0; k < this.zoomLevels; k++) {
+					var bc = binCount * Math.pow(2, k);
+					var a = 2 * l / bc;
+					var binX = Math.floor((p.x + l) / (2 * l) * bc);
+					var binY = Math.floor((p.y + l) / (2 * l) * bc);
+					if ( typeof bins[k]['' + binX] == 'undefined') {
+						bins[k]['' + binX] = [];
+					}
+					if ( typeof bins[k][''+binX]['' + binY] == 'undefined') {
+						bins[k][''+binX]['' + binY] = [];
+						for (var z = 0; z < this.objects.length; z++) {
+							bins[k][''+binX]['' + binY].push([]);
+						}
+						var x = binX * a + a / 2 - l;
+						var y = binY * a + a / 2 - l;
+						binData[k].push({
+							bin : bins[k][''+binX]['' + binY],
+							x : x,
+							y : y,
+							a : a,
+							r : a / 2
+						});
+					}
+					bins[k][''+binX][''+binY][i].push(o);
+				}
+			}
+		}
+
+		this.setCircleSet('square', binData);
+
+	},
+
+	triangularBinning : function() {
+
+		var l = 20037508.34;
+		var a0 = this.options.binCount;
+		var a1 = Math.sqrt(4 * a0 * a0 / Math.sqrt(3));
+		var binCount = a0 / a1 * a0;
+
+		var bins = [];
+		var binData = [];
+		for (var k = 0; k < this.zoomLevels; k++) {
+			bins.push([]);
+			binData.push([]);
+		}
+
+		for (var i = 0; i < this.objects.length; i++) {
+			for (var j = 0; j < this.objects[i].length; j++) {
+				var o = this.objects[i][j];
+				if (!o.isGeospatial) {
+					continue;
+				}
+				var p = new OpenLayers.Geometry.Point(o.getLongitude(this.options.mapIndex), o.getLatitude(this.options.mapIndex), null);
+				p.transform(this.map.displayProjection, this.map.projection);
+				o.x = p.x;
+				o.y = p.y;
+				for (var k = 0; k < this.zoomLevels; k++) {
+					var x_bc = binCount * Math.pow(2, k);
+					var y_bc = x_bc * x_bc / Math.sqrt(x_bc * x_bc - x_bc * x_bc / 4);
+					var a = 2 * l / x_bc;
+					var h = 2 * l / y_bc;
+					var binY = Math.floor((p.y + l) / (2 * l) * y_bc);
+					if ( typeof bins[k]['' + binY] == 'undefined') {
+						bins[k]['' + binY] = [];
+					}
+					var triangleIndex;
+					var partitionsX = x_bc * 2;
+					var partition = Math.floor((p.x + l) / (2 * l) * partitionsX);
+					var xMax = a / 2;
+					var yMax = h;
+					var x = p.x + l - partition * a / 2;
+					var y = p.y + l - binY * h;
+					if (binY % 2 == 0 && partition % 2 == 1 || binY % 2 == 1 && partition % 2 == 0) {
+						if (y + yMax / xMax * x < yMax) {
+							triangleIndex = partition;
+						} else {
+							triangleIndex = partition + 1;
+						}
+					} else {
+						if (y > yMax / xMax * x) {
+							triangleIndex = partition;
+						} else {
+							triangleIndex = partition + 1;
+						}
+					}
+					if ( typeof bins[k][''+binY]['' + triangleIndex] == 'undefined') {
+						bins[k][''+binY]['' + triangleIndex] = [];
+						for (var z = 0; z < this.objects.length; z++) {
+							bins[k][''+binY]['' + triangleIndex].push([]);
+						}
+						var r = Math.sqrt(3) / 6 * a;
+						var x = (triangleIndex - 1) * a / 2 + a / 2 - l;
+						var y;
+						if (binY % 2 == 0 && triangleIndex % 2 == 0 || binY % 2 == 1 && triangleIndex % 2 == 1) {
+							y = binY * h + h - r - l;
+						} else {
+							y = binY * h + r - l;
+						}
+						binData[k].push({
+							bin : bins[k][''+binY]['' + triangleIndex],
+							x : x,
+							y : y,
+							a : a,
+							r : r
+						});
+					}
+					bins[k][''+binY][''+triangleIndex][i].push(o);
+				}
+			}
+		}
+
+		this.setCircleSet('triangular', binData);
+
+	},
+
+	hexagonalBinning : function() {
+
+		var l = 20037508.34;
+		var a0 = this.options.binCount;
+		var a2 = Math.sqrt(4 * a0 * a0 / Math.sqrt(3)) / Math.sqrt(6);
+		var binCount = a0 / a2 * a0;
+
+		var bins = [];
+		var binData = [];
+		for (var k = 0; k < this.zoomLevels; k++) {
+			bins.push([]);
+			binData.push([]);
+		}
+
+		for (var i = 0; i < this.objects.length; i++) {
+			for (var j = 0; j < this.objects[i].length; j++) {
+				var o = this.objects[i][j];
+				if (!o.isGeospatial) {
+					continue;
+				}
+				var p = new OpenLayers.Geometry.Point(o.getLongitude(this.options.mapIndex), o.getLatitude(this.options.mapIndex), null);
+				p.transform(this.map.displayProjection, this.map.projection);
+				o.x = p.x;
+				o.y = p.y;
+				for (var k = 0; k < this.zoomLevels; k++) {
+					var x_bc = binCount * Math.pow(2, k);
+					var y_bc = x_bc * x_bc / Math.sqrt(x_bc * x_bc - x_bc * x_bc / 4);
+					var a = 2 * l / x_bc;
+					var h = 2 * l / y_bc;
+					var binY = Math.floor((p.y + l) / (2 * l) * y_bc);
+					if ( typeof bins[k]['' + binY] == 'undefined') {
+						bins[k]['' + binY] = [];
+					}
+					var triangleIndex;
+					var partitionsX = x_bc * 2;
+					var partition = Math.floor((p.x + l) / (2 * l) * partitionsX);
+					var xMax = a / 2;
+					var yMax = h;
+					var x = p.x + l - partition * a / 2;
+					var y = p.y + l - binY * h;
+					if (binY % 2 == 0 && partition % 2 == 1 || binY % 2 == 1 && partition % 2 == 0) {
+						if (y + yMax / xMax * x < yMax) {
+							triangleIndex = partition;
+						} else {
+							triangleIndex = partition + 1;
+						}
+					} else {
+						if (y > yMax / xMax * x) {
+							triangleIndex = partition;
+						} else {
+							triangleIndex = partition + 1;
+						}
+					}
+					if ( typeof bins[k][''+binY]['' + triangleIndex] == 'undefined') {
+						bins[k][''+binY]['' + triangleIndex] = [];
+						for (var z = 0; z < this.objects.length; z++) {
+							bins[k][''+binY]['' + triangleIndex].push([]);
+						}
+						var r = Math.sqrt(3) / 6 * a;
+						var x = (triangleIndex - 1) * a / 2 + a / 2 - l;
+						var y;
+						if (binY % 2 == 0 && triangleIndex % 2 == 0 || binY % 2 == 1 && triangleIndex % 2 == 1) {
+							y = binY * h + h - r - l;
+						} else {
+							y = binY * h + r - l;
+						}
+						binData[k].push({
+							bin : bins[k][''+binY]['' + triangleIndex],
+							x : x,
+							y : y,
+							a : a,
+							r : r,
+							h : h,
+							binX : triangleIndex,
+							binY : binY
+						});
+					}
+					bins[k][''+binY][''+triangleIndex][i].push(o);
+				}
+			}
+		}
+
+		var hexaBins = [];
+		var hexaBinData = [];
+		for (var k = 0; k < this.zoomLevels; k++) {
+			hexaBins.push([]);
+			hexaBinData.push([]);
+		}
+
+		for (var i = 0; i < binData.length; i++) {
+			for (var j = 0; j < binData[i].length; j++) {
+				var bin = binData[i][j];
+				var binY = Math.floor(bin.binY / 2);
+				var binX = Math.floor(bin.binX / 3);
+				var x, y;
+				var a = bin.a;
+				var h = bin.h;
+				if (bin.binX % 6 < 3) {
+					if ( typeof hexaBins[i]['' + binY] == 'undefined') {
+						hexaBins[i]['' + binY] = [];
+					}
+					y = binY * 2 * bin.h + bin.h - l;
+					x = binX * 1.5 * bin.a + a / 2 - l;
+				} else {
+					if (bin.binY % 2 == 1) {
+						binY++;
+					}
+					if ( typeof hexaBins[i]['' + binY] == 'undefined') {
+						hexaBins[i]['' + binY] = [];
+					}
+					y = binY * 2 * bin.h - l;
+					x = binX * 1.5 * bin.a + a / 2 - l;
+				}
+				if ( typeof hexaBins[i][''+binY]['' + binX] == 'undefined') {
+					hexaBins[i][''+binY]['' + binX] = [];
+					for (var z = 0; z < this.objects.length; z++) {
+						hexaBins[i][''+binY]['' + binX].push([]);
+					}
+					hexaBinData[i].push({
+						bin : hexaBins[i][''+binY]['' + binX],
+						x : x,
+						y : y,
+						a : bin.a,
+						r : bin.h
+					});
+				}
+				for (var k = 0; k < bin.bin.length; k++) {
+					for (var m = 0; m < bin.bin[k].length; m++) {
+						hexaBins[i][''+binY][''+binX][k].push(bin.bin[k][m]);
+					}
+				}
+			}
+		}
+
+		this.setCircleSet('hexagonal', hexaBinData);
+
+	}
+}
+