marchingsquares
Version:
MarchingSquaresJS - An implementation of the Marching Squares algorithm featuring Isocontour and Isoband computation.
1,683 lines (1,458 loc) • 72.6 kB
JavaScript
/*!
* MarchingSquaresJS
* version 1.3.3
* https://github.com/RaumZeit/MarchingSquares.js
*
* @license GNU Affero General Public License.
* Copyright (c) 2015-2019 Ronny Lorenz <ronny@tbi.univie.ac.at>
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.MarchingSquaresJS = global.MarchingSquaresJS || {})));
}(this, (function (exports) { 'use strict';
/*
* Compute the distance of a pair of values ('v0', 'v1') from 'a' through linear interpolation
* between the values of 'a' and 'b'
*
* This function assumes that exactly one value, 'v0' or 'v1', is actually located
* between 'a' and 'b', and choses the right one automagically
*
* Note, that we assume that 'a' and 'b' have unit distance (i.e. 1)
*/
function linear_ab(a, b, v0, v1) {
var tmp;
if (v0 > v1) {
tmp = v0;
v0 = v1;
v1 = tmp;
}
if (a < b) {
if (a < v0)
return (v0 - a) / (b - a);
else
return (v1 - a) / (b - a);
} else if (a > v1) {
return (a - v1) / (a - b);
}
return (a - v0) / (a - b);
}
/*
* Compute the distance of a pair of values ('v0', 'v1') from 'a' through linear interpolation
* between the values of 'a' and 'b'
*
* This function automagically choses the value 'vN' that is closer to 'a'
*
* Note, that we assume that 'a' and 'b' have unit distance (i.e. 1)
*/
function linear_a(a, b, minV, maxV) {
if (a < b)
return (minV - a) / (b - a);
return (a - maxV) / (a - b);
}
/*
* Compute the distance of a pair of values ('v0', 'v1') from 'a' through linear interpolation
* between the values of 'a' and 'b'
*
* This function automagically choses the value 'vN' that is closer to 'b'
*
* Note, that we assume that 'a' and 'b' have unit distance (i.e. 1)
*/
function linear_b(a, b, minV, maxV) {
if (a < b)
return (maxV - a) / (b - a);
return (a - minV) / (a - b);
}
function Options() {
/* Settings common to all implemented algorithms */
this.successCallback = null;
this.verbose = false;
this.polygons = false;
this.polygons_full = false;
this.linearRing = true;
this.noQuadTree = false;
this.noFrame = false;
}
/* Compose settings specific to IsoBands algorithm */
function isoBandOptions(userSettings) {
var i,
key,
val,
bandOptions,
optionKeys;
bandOptions = new Options();
userSettings = userSettings ? userSettings : {};
optionKeys = Object.keys(bandOptions);
for(i = 0; i < optionKeys.length; i++) {
key = optionKeys[i];
val = userSettings[key];
if ((typeof val !== 'undefined') && (val !== null))
bandOptions[key] = val;
}
/* restore compatibility */
bandOptions.polygons_full = !bandOptions.polygons;
/* add interpolation functions (not yet user customizable) */
bandOptions.interpolate = linear_ab;
bandOptions.interpolate_a = linear_a;
bandOptions.interpolate_b = linear_b;
return bandOptions;
}
function cell2Polygons(cell, x, y, settings) {
var polygons = [];
cell.polygons.forEach(function(p) {
p.forEach(function(pp) {
pp[0] += x;
pp[1] += y;
});
if (settings.linearRing)
p.push(p[0]);
polygons.push(p);
});
return polygons;
}
function entry_coordinate(x, y, mode, path) {
if (mode === 0) { /* down */
x += 1;
y += path[0][1];
} else if (mode === 1) { /* left */
x += path[0][0];
} else if (mode === 2) { /* up */
y += path[0][1];
} else if (mode === 3) { /* right */
x += path[0][0];
y += 1;
}
return [ x, y ];
}
function skip_coordinate(x, y, mode) {
if (mode === 0) { /* down */
x++;
} else if (mode === 1) ; else if (mode === 2) { /* up */
y++;
} else if (mode === 3) { /* right */
x++;
y++;
}
return [ x, y ];
}
function requireFrame(data, lowerBound, upperBound) {
var frameRequired,
cols,
rows,
i,
j;
frameRequired = true;
cols = data[0].length;
rows = data.length;
for (j = 0; j < rows; j++) {
if ((data[j][0] < lowerBound) ||
(data[j][0] > upperBound) ||
(data[j][cols - 1] < lowerBound) ||
(data[j][cols - 1] > upperBound)) {
frameRequired = false;
break;
}
}
if ((frameRequired) &&
((data[rows - 1][0] < lowerBound) ||
(data[rows - 1][0] > upperBound) ||
(data[rows - 1][cols - 1] < lowerBound) ||
(data[rows - 1][cols - 1] > upperBound))) {
frameRequired = false;
}
if (frameRequired)
for (i = 0; i < cols - 1; i++) {
if ((data[0][i] < lowerBound) ||
(data[0][i] > upperBound) ||
(data[rows - 1][i] < lowerBound) ||
(data[rows - 1][i] > upperBound)) {
frameRequired = false;
break;
}
}
return frameRequired;
}
function traceBandPaths(data, cellGrid, settings) {
var nextedge,
path,
e,
ee,
s,
ve,
enter,
x,
y,
finalized,
origin,
cc,
dir,
count,
point,
found_entry;
var polygons = [];
var rows = data.length - 1;
var cols = data[0].length - 1;
/*
* directions for out-of-grid moves are:
* 0 ... "down",
* 1 ... "left",
* 2 ... "up",
* 3 ... "right"
*/
var valid_entries = [ ['rt', 'rb'], /* down */
['br', 'bl'], /* left */
['lb', 'lt'], /* up */
['tl', 'tr'] /* right */
];
var add_x = [ 0, -1, 0, 1 ];
var add_y = [ -1, 0, 1, 0 ];
var available_starts = [ 'bl', 'lb', 'lt', 'tl', 'tr', 'rt', 'rb', 'br' ];
var entry_dir = {
bl: 1, br: 1,
lb: 2, lt: 2,
tl: 3, tr: 3,
rt: 0, rb: 0
};
if (requireFrame(data, settings.minV, settings.maxV)) {
if (settings.linearRing)
polygons.push([ [0, 0], [0, rows], [cols, rows], [cols, 0], [0, 0] ]);
else
polygons.push([ [0, 0], [0, rows], [cols, rows], [cols, 0] ]);
}
/* finally, start tracing back first polygon(s) */
cellGrid.forEach(function(a, i) {
a.forEach(function(cell, j) {
nextedge = null;
/* trace paths for all available edges that go through this cell */
for (e = 0; e < 8; e++) {
nextedge = available_starts[e];
if (typeof cell.edges[nextedge] !== 'object')
continue;
/* start a new, full path */
path = [];
ee = cell.edges[nextedge];
enter = nextedge;
x = i;
y = j;
finalized = false;
origin = [ i + ee.path[0][0], j + ee.path[0][1] ];
/* add start coordinate */
path.push(origin);
/* start traceback */
while (!finalized) {
cc = cellGrid[x][y];
if (typeof cc.edges[enter] !== 'object')
break;
ee = cc.edges[enter];
/* remove edge from cell */
delete cc.edges[enter];
/* add last point of edge to path arra, since we extend a polygon */
point = ee.path[1];
point[0] += x;
point[1] += y;
path.push(point);
enter = ee.move.enter;
x = x + ee.move.x;
y = y + ee.move.y;
/* handle out-of-grid moves */
if ((typeof cellGrid[x] === 'undefined') ||
(typeof cellGrid[x][y] === 'undefined')) {
dir = 0;
count = 0;
if (x === cols) {
x--;
dir = 0; /* move downwards */
} else if (x < 0) {
x++;
dir = 2; /* move upwards */
} else if (y === rows) {
y--;
dir = 3; /* move right */
} else if (y < 0) {
y++;
dir = 1; /* move left */
} else {
throw new Error('Left the grid somewhere in the interior!');
}
if ((x === i) && (y === j) && (dir === entry_dir[nextedge])) {
finalized = true;
enter = nextedge;
break;
}
while (1) {
found_entry = false;
if (count > 4)
throw new Error('Direction change counter overflow! This should never happen!');
if (!((typeof cellGrid[x] === 'undefined') ||
(typeof cellGrid[x][y] === 'undefined'))) {
cc = cellGrid[x][y];
/* check for re-entry */
for (s = 0; s < valid_entries[dir].length; s++) {
ve = valid_entries[dir][s];
if (typeof cc.edges[ve] === 'object') {
/* found re-entry */
ee = cc.edges[ve];
path.push(entry_coordinate(x, y, dir, ee.path));
enter = ve;
found_entry = true;
break;
}
}
}
if (found_entry) {
break;
} else {
path.push(skip_coordinate(x, y, dir));
x += add_x[dir];
y += add_y[dir];
/* change direction if we'e moved out of grid again */
if ((typeof cellGrid[x] === 'undefined') ||
(typeof cellGrid[x][y] === 'undefined')) {
if (((dir === 0) && (y < 0)) ||
((dir === 1) && (x < 0)) ||
((dir === 2) && (y === rows)) ||
((dir === 3) && (x === cols))) {
x -= add_x[dir];
y -= add_y[dir];
dir = (dir + 1) % 4;
count++;
}
}
if ((x === i) && (y === j) && (dir === entry_dir[nextedge])) {
/* we are back where we started off, so finalize the polygon */
finalized = true;
enter = nextedge;
break;
}
}
}
}
}
if ((settings.linearRing) &&
((path[path.length - 1][0] !== origin[0]) ||
(path[path.length - 1][1] !== origin[1])))
path.push(origin);
polygons.push(path);
} /* end forall entry sites */
}); /* end foreach i */
}); /* end foreach j */
return polygons;
}
/* quadTree node constructor */
function TreeNode(data, x, y, dx, dy) {
var dx_tmp = dx,
dy_tmp = dy,
msb_x = 0,
msb_y = 0;
/* left-bottom corner of current quadrant */
this.x = x;
this.y = y;
/* minimum value in subtree under this node */
this.lowerBound = null;
/* maximum value in subtree under this node */
this.upperBound = null;
/*
* child nodes are layed out in the following way:
*
* (x, y + 1) ---- (x + 1, y + 1)
* | | |
* | D | C |
* | | |
* |----------------------------|
* | | |
* | A | B |
* | | |
* (x, y) ------------ (x + 1, y)
*/
this.childA = null;
this.childB = null;
this.childC = null;
this.childD = null;
if ((dx === 1) && (dy === 1)) {
/* do not further subdivision */
this.lowerBound = Math.min(
data[y][x],
data[y][x + 1],
data[y + 1][x + 1],
data[y + 1][x]
);
this.upperBound = Math.max(
data[y][x],
data[y][x + 1],
data[y + 1][x + 1],
data[y + 1][x]
);
} else {
/* get most significant bit from dx */
if (dx > 1) {
while (dx_tmp !== 0) {
dx_tmp = dx_tmp >> 1;
msb_x++;
}
if (dx === (1 << (msb_x - 1)))
msb_x--;
dx_tmp = 1 << (msb_x - 1);
}
/* get most significant bit from dx */
if (dy > 1) {
while (dy_tmp !== 0) {
dy_tmp = dy_tmp >> 1;
msb_y++;
}
if (dy === (1 << (msb_y - 1)))
msb_y--;
dy_tmp = 1 << (msb_y - 1);
}
this.childA = new TreeNode(data, x, y, dx_tmp, dy_tmp);
this.lowerBound = this.childA.lowerBound;
this.upperBound = this.childA.upperBound;
if (dx - dx_tmp > 0) {
this.childB = new TreeNode(data, x + dx_tmp, y, dx - dx_tmp, dy_tmp);
this.lowerBound = Math.min(this.lowerBound, this.childB.lowerBound);
this.upperBound = Math.max(this.upperBound, this.childB.upperBound);
if (dy - dy_tmp > 0) {
this.childC = new TreeNode(data, x + dx_tmp, y + dy_tmp, dx - dx_tmp, dy - dy_tmp);
this.lowerBound = Math.min(this.lowerBound, this.childC.lowerBound);
this.upperBound = Math.max(this.upperBound, this.childC.upperBound);
}
}
if (dy - dy_tmp > 0) {
this.childD = new TreeNode(data, x, y + dy_tmp, dx_tmp, dy - dy_tmp);
this.lowerBound = Math.min(this.lowerBound, this.childD.lowerBound);
this.upperBound = Math.max(this.upperBound, this.childD.upperBound);
}
}
}
/**
* Retrieve a list of cells within a particular range of values by
* recursivly traversing the quad tree to it's leaves.
*
* @param subsumed If 'true' include all cells that are completely
* subsumed within the specified range. Otherwise,
* return only cells where at least one corner is
* outside the specified range.
*
* @return An array of objects 'o' where each object has exactly two
* properties: 'o.x' and 'o.y' denoting the left-bottom corner
* of the corresponding cell.
*/
TreeNode.prototype.cellsInBand = function(lowerBound, upperBound, subsumed) {
var cells = [];
subsumed = (typeof subsumed === 'undefined') ? true : subsumed;
if ((this.lowerBound > upperBound) || (this.upperBound < lowerBound))
return cells;
if (!(this.childA || this.childB || this.childC || this.childD)) {
if ((subsumed) ||
(this.lowerBound <= lowerBound) ||
(this.upperBound >= upperBound)) {
cells.push({
x: this.x,
y: this.y
});
}
} else {
if (this.childA)
cells = cells.concat(this.childA.cellsInBand(lowerBound, upperBound, subsumed));
if (this.childB)
cells = cells.concat(this.childB.cellsInBand(lowerBound, upperBound, subsumed));
if (this.childD)
cells = cells.concat(this.childD.cellsInBand(lowerBound, upperBound, subsumed));
if (this.childC)
cells = cells.concat(this.childC.cellsInBand(lowerBound, upperBound, subsumed));
}
return cells;
};
TreeNode.prototype.cellsBelowThreshold = function(threshold, subsumed) {
var cells = [];
subsumed = (typeof subsumed === 'undefined') ? true : subsumed;
if (this.lowerBound > threshold)
return cells;
if (!(this.childA || this.childB || this.childC || this.childD)) {
if ((subsumed) ||
(this.upperBound >= threshold)) {
cells.push({
x: this.x,
y: this.y
});
}
} else {
if (this.childA)
cells = cells.concat(this.childA.cellsBelowThreshold(threshold, subsumed));
if (this.childB)
cells = cells.concat(this.childB.cellsBelowThreshold(threshold, subsumed));
if (this.childD)
cells = cells.concat(this.childD.cellsBelowThreshold(threshold, subsumed));
if (this.childC)
cells = cells.concat(this.childC.cellsBelowThreshold(threshold, subsumed));
}
return cells;
};
/*
* Given a scalar field `data` construct a QuadTree
* to efficiently lookup those parts of the scalar
* field where values are within a particular
* range of [lowerbound, upperbound] limits.
*/
function QuadTree(data) {
var i, cols;
/* do some input checking */
if (!data)
throw new Error('data is required');
if (!Array.isArray(data) ||
!Array.isArray(data[0]))
throw new Error('data must be scalar field, i.e. array of arrays');
if (data.length < 2)
throw new Error('data must contain at least two rows');
/* check if we've got a regular grid */
cols = data[0].length;
if (cols < 2)
throw new Error('data must contain at least two columns');
for (i = 1; i < data.length; i++) {
if (!Array.isArray(data[i]))
throw new Error('Row ' + i + ' is not an array');
if (data[i].length != cols)
throw new Error('unequal row lengths detected, please provide a regular grid');
}
/* create pre-processing object */
this.data = data;
/* root node, i.e. entry to the data */
this.root = new TreeNode(data, 0, 0, data[0].length - 1, data.length - 1);
}
/* eslint no-console: ["error", { allow: ["log"] }] */
/*
* lookup table to generate polygon paths or edges required to
* trace the full polygon(s)
*/
var shapeCoordinates = {
square: function(cell, x0, x1, x2, x3, opt) {
if (opt.polygons)
cell.polygons.push([ [0,0], [0, 1], [1, 1], [1, 0] ]);
},
triangle_bl: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var leftbottom = opt.interpolate(x0, x3, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lb = {
path: [ [0, leftbottom], [bottomleft, 0] ],
move: {
x: 0,
y: -1,
enter: 'tl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, leftbottom], [bottomleft, 0], [0, 0] ]);
},
triangle_br: function(cell, x0, x1, x2, x3, opt) {
var bottomright = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var rightbottom = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.br = {
path: [ [bottomright, 0], [1, rightbottom] ],
move: {
x: 1,
y: 0,
enter: 'lb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomright, 0], [1, rightbottom], [1, 0] ]);
},
triangle_tr: function(cell, x0, x1, x2, x3, opt) {
var righttop = opt.interpolate(x1, x2, opt.minV, opt.maxV);
var topright = opt.interpolate(x3, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.rt = {
path: [ [1, righttop], [topright, 1] ],
move: {
x: 0,
y: 1,
enter: 'br'
}
};
}
if (opt.polygons)
cell.polygons.push([ [1, righttop], [topright, 1], [1, 1] ]);
},
triangle_tl: function(cell, x0, x1, x2, x3, opt) {
var topleft = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var lefttop = opt.interpolate(x0, x3, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.tl = {
path: [ [topleft, 1], [0, lefttop] ],
move: {
x: -1,
y: 0,
enter: 'rt'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, lefttop], [0, 1], [topleft, 1] ]);
},
tetragon_t: function(cell, x0, x1, x2, x3, opt) {
var righttop = opt.interpolate(x1, x2, opt.minV, opt.maxV);
var lefttop = opt.interpolate(x0, x3, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.rt = {
path: [ [1, righttop], [0, lefttop] ],
move: {
x: -1,
y: 0,
enter: 'rt'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, lefttop], [0, 1], [1, 1], [1, righttop] ]);
},
tetragon_r: function(cell, x0, x1, x2, x3, opt) {
var bottomright = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var topright = opt.interpolate(x3, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.br = {
path: [ [bottomright, 0], [topright, 1] ],
move: {
x: 0,
y: 1,
enter: 'br'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomright, 0], [topright, 1], [1, 1], [1, 0] ]);
},
tetragon_b: function(cell, x0, x1, x2, x3, opt) {
var leftbottom = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var rightbottom = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lb = {
path: [ [0, leftbottom], [1, rightbottom] ],
move: {
x: 1,
y: 0,
enter: 'lb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, leftbottom], [1, rightbottom], [1, 0] ]);
},
tetragon_l: function(cell, x0, x1, x2, x3, opt) {
var topleft = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var bottomleft = opt.interpolate(x0, x1, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.tl = {
path: [ [topleft, 1], [bottomleft, 0] ],
move: {
x: 0,
y: -1,
enter: 'tl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, 1], [topleft, 1], [bottomleft, 0] ]);
},
tetragon_bl: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate_a(x0, x1, opt.minV, opt.maxV);
var bottomright = opt.interpolate_b(x0, x1, opt.minV, opt.maxV);
var leftbottom = opt.interpolate_a(x0, x3, opt.minV, opt.maxV);
var lefttop = opt.interpolate_b(x0, x3, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.bl = {
path: [ [bottomleft, 0], [0, leftbottom] ],
move: {
x: -1,
y: 0,
enter: 'rb'
}
};
cell.edges.lt = {
path: [ [0, lefttop], [bottomright, 0] ],
move: {
x: 0,
y: -1,
enter: 'tr'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomleft, 0], [0, leftbottom], [0, lefttop], [bottomright, 0] ]);
},
tetragon_br: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate_a(x0, x1, opt.minV, opt.maxV);
var bottomright = opt.interpolate_b(x0, x1, opt.minV, opt.maxV);
var rightbottom = opt.interpolate_a(x1, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate_b(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.bl = {
path: [ [bottomleft, 0], [1, righttop] ],
move: {
x: 1,
y: 0,
enter: 'lt'
}
};
cell.edges.rb = {
path: [ [1, rightbottom], [bottomright, 0] ],
move: {
x: 0,
y: -1,
enter: 'tr'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomleft, 0], [1, righttop], [1, rightbottom], [bottomright, 0] ]);
},
tetragon_tr: function(cell, x0, x1, x2, x3, opt) {
var topleft = opt.interpolate_a(x3, x2, opt.minV, opt.maxV);
var topright = opt.interpolate_b(x3, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate_b(x1, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate_a(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.rb = {
path: [ [1, rightbottom], [topleft, 1] ],
move: {
x: 0,
y: 1,
enter: 'bl'
}
};
cell.edges.tr = {
path: [ [topright, 1], [1, righttop] ],
move: {
x: 1,
y: 0,
enter: 'lt'
}
};
}
if (opt.polygons)
cell.polygons.push([ [1, rightbottom], [topleft, 1], [topright, 1], [1, righttop] ]);
},
tetragon_tl: function(cell, x0, x1, x2, x3, opt) {
var topleft = opt.interpolate_a(x3, x2, opt.minV, opt.maxV);
var topright = opt.interpolate_b(x3, x2, opt.minV, opt.maxV);
var lefttop = opt.interpolate_b(x0, x3, opt.minV, opt.maxV);
var leftbottom = opt.interpolate_a(x0, x3, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.tr = {
path: [ [topright, 1], [0, leftbottom] ],
move: {
x: -1,
y: 0,
enter: 'rb'
}
};
cell.edges.lt = {
path: [ [0, lefttop], [topleft, 1] ],
move: {
x: 0,
y: 1,
enter: 'bl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [topright, 1], [0, leftbottom], [0, lefttop], [topleft, 1] ]);
},
tetragon_lr: function(cell, x0, x1, x2, x3, opt) {
var leftbottom = opt.interpolate_a(x0, x3, opt.minV, opt.maxV);
var lefttop = opt.interpolate_b(x0, x3, opt.minV, opt.maxV);
var righttop = opt.interpolate_b(x1, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate_a(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lt = {
path: [ [0, lefttop], [1, righttop] ],
move: {
x: 1,
y: 0,
enter: 'lt'
}
};
cell.edges.rb = {
path: [ [1, rightbottom], [0, leftbottom] ],
move: {
x: -1,
y: 0,
enter: 'rb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, leftbottom], [0, lefttop], [1, righttop], [1, rightbottom] ]);
},
tetragon_tb: function(cell, x0, x1, x2, x3, opt) {
var topleft = opt.interpolate_a(x3, x2, opt.minV, opt.maxV);
var topright = opt.interpolate_b(x3, x2, opt.minV, opt.maxV);
var bottomright = opt.interpolate_b(x0, x1, opt.minV, opt.maxV);
var bottomleft = opt.interpolate_a(x0, x1, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.tr = {
path: [ [topright, 1], [bottomright, 0] ],
move: {
x: 0,
y: -1,
enter: 'tr'
}
};
cell.edges.bl = {
path: [ [bottomleft, 0], [topleft, 1] ],
move: {
x: 0,
y: 1,
enter: 'bl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomleft, 0], [topleft, 1], [topright, 1], [bottomright, 0] ]);
},
pentagon_tr: function(cell, x0, x1, x2, x3, opt) {
var topleft = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.tl = {
path: [[topleft, 1], [1, rightbottom]],
move: {
x: 1,
y: 0,
enter: 'lb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, 1], [topleft, 1], [1, rightbottom], [1, 0] ]);
},
pentagon_tl: function(cell, x0, x1, x2, x3, opt) {
var leftbottom = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var topright = opt.interpolate(x3, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lb = {
path: [ [0, leftbottom], [topright, 1] ],
move: {
x: 0,
y: 1,
enter: 'br'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, leftbottom], [topright, 1], [1, 1], [1, 0] ]);
},
pentagon_br: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var righttop = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.rt = {
path: [ [1, righttop], [bottomleft, 0] ],
move: {
x: 0,
y: -1,
enter: 'tl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, 1], [1, 1], [1, righttop], [bottomleft, 0] ]);
},
pentagon_bl: function(cell, x0, x1, x2, x3, opt) {
var lefttop = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var bottomright = opt.interpolate(x0, x1, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.br = {
path: [ [bottomright, 0], [0, lefttop] ],
move: {
x: -1,
y: 0,
enter: 'rt'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, lefttop], [0, 1], [1, 1], [1, 0], [bottomright, 0] ]);
},
pentagon_tr_rl: function(cell, x0, x1, x2, x3, opt) {
var lefttop = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var topleft = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate_b(x1, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate_a(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.tl = {
path: [ [topleft, 1], [1, righttop] ],
move: {
x: 1,
y: 0,
enter: 'lt'
}
};
cell.edges.rb = {
path: [ [1, rightbottom], [0, lefttop] ],
move: {
x: -1,
y: 0,
enter: 'rt'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, lefttop], [0, 1], [topleft, 1], [1, righttop], [1, rightbottom] ]);
},
pentagon_rb_bt: function(cell, x0, x1, x2, x3, opt) {
var righttop = opt.interpolate(x1, x2, opt.minV, opt.maxV);
var bottomright = opt.interpolate_b(x0, x1, opt.minV, opt.maxV);
var bottomleft = opt.interpolate_a(x0, x1, opt.minV, opt.maxV);
var topright = opt.interpolate(x3, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.rt = {
path: [ [1, righttop], [bottomright, 0] ],
move: {
x: 0,
y: -1,
enter: 'tr'
}
};
cell.edges.bl = {
path: [ [bottomleft, 0], [topright, 1] ],
move: {
x: 0,
y: 1,
enter: 'br'
}
};
}
if (opt.polygons)
cell.polygons.push([ [topright, 1], [1, 1], [1, righttop], [bottomright, 0], [bottomleft, 0] ]);
},
pentagon_bl_lr: function(cell, x0, x1, x2, x3, opt) {
var bottomright = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var leftbottom = opt.interpolate_a(x0, x3, opt.minV, opt.maxV);
var lefttop = opt.interpolate_b(x0, x3, opt.minV, opt.maxV);
var rightbottom = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.br = {
path: [ [bottomright, 0], [0, leftbottom] ],
move: {
x: -1,
y: 0,
enter: 'rb'
}
};
cell.edges.lt = {
path: [ [0, lefttop], [1, rightbottom] ],
move: {
x: 1,
y: 0,
enter: 'lb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomright, 0], [0, leftbottom], [0, lefttop], [1, rightbottom], [1, 0] ]);
},
pentagon_lt_tb: function(cell, x0, x1, x2, x3, opt) {
var leftbottom = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var topleft = opt.interpolate_a(x3, x2, opt.minV, opt.maxV);
var topright = opt.interpolate_b(x3, x2, opt.minV, opt.maxV);
var bottomleft = opt.interpolate(x0, x1, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lb = {
path: [ [0, leftbottom], [topleft, 1] ],
move: {
x: 0,
y: 1,
enter: 'bl'
}
};
cell.edges.tr = {
path: [ [topright, 1], [bottomleft, 0] ],
move: {
x: 0,
y: -1,
enter: 'tl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, leftbottom], [topleft, 1], [topright, 1], [bottomleft, 0] ]);
},
pentagon_bl_tb: function(cell, x0, x1, x2, x3, opt) {
var lefttop = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var topleft = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var bottomright = opt.interpolate_b(x0, x1, opt.minV, opt.maxV);
var bottomleft = opt.interpolate_a(x0, x1, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.bl = {
path: [ [bottomleft, 0], [0, lefttop] ],
move: {
x: -1,
y: 0,
enter: 'rt'
}
};
cell.edges.tl = {
path: [ [ topleft, 1], [bottomright, 0] ],
move: {
x: 0,
y: -1,
enter: 'tr'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, lefttop], [0, 1], [topleft, 1], [bottomright, 0], [bottomleft, 0] ]);
},
pentagon_lt_rl: function(cell, x0, x1, x2, x3, opt) {
var leftbottom = opt.interpolate_a(x0, x3, opt.minV, opt.maxV);
var lefttop = opt.interpolate_b(x0, x3, opt.minV, opt.maxV);
var topright = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate(x1, x3, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lt = {
path: [ [0, lefttop], [topright, 1] ],
move: {
x: 0,
y: 1,
enter: 'br'
}
};
cell.edges.rt = {
path: [ [1, righttop], [0, leftbottom] ],
move: {
x: -1,
y: 0,
enter: 'rb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, leftbottom], [0, lefttop], [topright, 1], [1, 1], [1, righttop] ]);
},
pentagon_tr_bt: function(cell, x0, x1, x2, x3, opt) {
var topleft = opt.interpolate_a(x3, x2, opt.minV, opt.maxV);
var topright = opt.interpolate_b(x3, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate(x1, x2, opt.minV, opt.maxV);
var bottomright = opt.interpolate(x0, x1, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.br = {
path: [ [bottomright, 0], [topleft, 1] ],
move: {
x: 0,
y: 1,
enter: 'bl'
}
};
cell.edges.tr = {
path: [ [topright, 1], [1, rightbottom] ],
move: {
x: 1,
y: 0,
enter: 'lb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [topleft, 1], [topright, 1], [1, rightbottom], [1, 0], [bottomright, 0] ]);
},
pentagon_rb_lr: function(cell, x0, x1, x2, x3, opt) {
var leftbottom = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var righttop = opt.interpolate_b(x1, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate_a(x1, x2, opt.minV, opt.maxV);
var bottomleft = opt.interpolate(x0, x1, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lb = {
path: [ [0, leftbottom], [1, righttop] ],
move: {
x: 1,
y: 0,
enter: 'lt'
}
};
cell.edges.rb = {
path: [ [1, rightbottom], [bottomleft, 0] ],
move: {
x: 0,
y: -1,
enter: 'tl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, leftbottom], [1, righttop], [1, rightbottom], [bottomleft, 0] ]);
},
hexagon_lt_tr: function(cell, x0, x1, x2, x3, opt) {
var leftbottom = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var topleft = opt.interpolate_a(x3, x2, opt.minV, opt.maxV);
var topright = opt.interpolate_b(x3, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lb = {
path: [ [0, leftbottom], [topleft, 1] ],
move: {
x: 0,
y: 1,
enter: 'bl'
}
};
cell.edges.tr = {
path: [ [topright, 1], [1, rightbottom] ],
move: {
x: 1,
y: 0,
enter: 'lb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, leftbottom], [topleft, 1], [topright, 1], [1, rightbottom], [1, 0] ]);
},
hexagon_bl_lt: function(cell, x0, x1, x2, x3, opt) {
var bottomright = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var leftbottom = opt.interpolate_a(x0, x3, opt.minV, opt.maxV);
var lefttop = opt.interpolate_b(x0, x3, opt.minV, opt.maxV);
var topright = opt.interpolate(x3, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.br = {
path: [ [bottomright, 0], [0, leftbottom] ],
move: {
x: -1,
y: 0,
enter: 'rb'
}
};
cell.edges.lt = {
path: [ [0, lefttop], [topright, 1] ],
move: {
x: 0,
y: 1,
enter: 'br'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomright, 0], [0, leftbottom], [0, lefttop], [topright, 1], [1, 1], [1, 0] ]);
},
hexagon_bl_rb: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate_a(x0, x1, opt.minV, opt.maxV);
var bottomright = opt.interpolate_b(x0, x1, opt.minV, opt.maxV);
var lefttop = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var righttop = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.bl = {
path: [ [bottomleft, 0], [0, lefttop] ],
move: {
x: -1,
y: 0,
enter: 'rt'
}
};
cell.edges.rt = {
path: [ [1, righttop], [bottomright, 0] ],
move: {
x: 0,
y: -1,
enter: 'tr'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomleft, 0], [0, lefttop], [0, 1], [1, 1], [1, righttop], [bottomright, 0] ]);
},
hexagon_tr_rb: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var topleft = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate_b(x1, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate_a(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.tl = {
path: [ [topleft, 1], [1, righttop] ],
move: {
x: 1,
y: 0,
enter: 'lt'
}
};
cell.edges.rb = {
path: [ [1, rightbottom], [bottomleft, 0] ],
move: {
x: 0,
y: -1,
enter: 'tl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, 1], [topleft, 1], [1, righttop], [1, rightbottom], [bottomleft, 0] ]);
},
hexagon_lt_rb: function(cell, x0, x1, x2, x3, opt) {
var leftbottom = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var topright = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate(x1, x2, opt.minV, opt.maxV);
var bottomleft = opt.interpolate(x0, x1, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lb = {
path: [ [0, leftbottom], [topright, 1] ],
move: {
x: 0,
y: 1,
enter: 'br'
}
};
cell.edges.rt = {
path: [ [1, righttop], [bottomleft, 0] ],
move: {
x: 0,
y: -1,
enter: 'tl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, leftbottom], [topright, 1], [1, 1], [1, righttop], [bottomleft, 0] ]);
},
hexagon_bl_tr: function(cell, x0, x1, x2, x3, opt) {
var bottomright = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var lefttop = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var topleft = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.br = {
path: [ [bottomright, 0], [0, lefttop] ],
move: {
x: -1,
y: 0,
enter: 'rt'
}
};
cell.edges.tl = {
path: [ [topleft, 1], [1, rightbottom] ],
move: {
x: 1,
y: 0,
enter: 'lb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomright, 0], [0, lefttop], [0, 1], [topleft, 1], [1, rightbottom], [1, 0] ]);
},
heptagon_tr: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate_a(x0, x1, opt.minV, opt.maxV);
var bottomright = opt.interpolate_b(x0, x1, opt.minV, opt.maxV);
var leftbottom = opt.interpolate_a(x0, x3, opt.minV, opt.maxV);
var lefttop = opt.interpolate_b(x0, x3, opt.minV, opt.maxV);
var topright = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.bl = {
path: [ [bottomleft, 0], [0, leftbottom] ],
move: {
x: -1,
y: 0,
enter: 'rb'
}
};
cell.edges.lt = {
path: [ [0, lefttop], [topright, 1] ],
move: {
x: 0,
y: 1,
enter: 'br'
}
};
cell.edges.rt = {
path: [ [1, righttop], [bottomright, 0] ],
move: {
x: 0,
y: -1,
enter: 'tr'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomleft, 0], [0, leftbottom], [0, lefttop], [topright, 1], [1, 1], [1, righttop], [bottomright, 0] ]);
},
heptagon_bl: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var leftbottom = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var topleft = opt.interpolate_a(x3, x2, opt.minV, opt.maxV);
var topright = opt.interpolate_b(x3, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate_b(x1, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate_a(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.lb = {
path: [ [0, leftbottom], [topleft, 1] ],
move: {
x: 0,
y: 1,
enter: 'bl'
}
};
cell.edges.tr = {
path: [ [topright, 1], [1, righttop] ],
move: {
x: 1,
y: 0,
enter: 'lt'
}
};
cell.edges.rb = {
path: [ [1, rightbottom], [bottomleft, 0] ],
move: {
x: 0,
y: -1,
enter: 'tl'
}
};
}
if (opt.polygons)
cell.polygons.push([ [0, 0], [0, leftbottom], [topleft, 1], [topright, 1], [1, righttop], [1, rightbottom], [bottomleft, 0] ]);
},
heptagon_tl: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate_a(x0, x1, opt.minV, opt.maxV);
var bottomright = opt.interpolate_b(x0, x1, opt.minV, opt.maxV);
var lefttop = opt.interpolate(x0, x3, opt.minV, opt.maxV);
var topleft = opt.interpolate(x3, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate_b(x1, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate_a(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.bl = {
path: [ [bottomleft, 0], [0, lefttop] ],
move: {
x: -1,
y: 0,
enter: 'rt'
}
};
cell.edges.tl = {
path: [ [topleft, 1], [1, righttop] ],
move: {
x: 1,
y: 0,
enter: 'lt'
}
};
cell.edges.rb = {
path: [ [1, rightbottom], [bottomright, 0] ],
move: {
x: 0,
y: -1,
enter: 'tr'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomleft, 0], [0, lefttop], [0, 1], [topleft, 1], [1, righttop], [1, rightbottom], [bottomright, 0] ]);
},
heptagon_br: function(cell, x0, x1, x2, x3, opt) {
var bottomright = opt.interpolate(x0, x1, opt.minV, opt.maxV);
var leftbottom = opt.interpolate_a(x0, x3, opt.minV, opt.maxV);
var lefttop = opt.interpolate_b(x0, x3, opt.minV, opt.maxV);
var topleft = opt.interpolate_a(x3, x2, opt.minV, opt.maxV);
var topright = opt.interpolate_b(x3, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.br = {
path: [ [bottomright, 0], [0, leftbottom] ],
move: {
x: -1,
y: 0,
enter: 'rb'
}
};
cell.edges.lt = {
path: [ [0, lefttop], [topleft, 1] ],
move: {
x: 0,
y: 1,
enter: 'bl'
}
};
cell.edges.tr = {
path: [ [topright, 1], [1, rightbottom] ],
move: {
x: 1,
y: 0,
enter: 'lb'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomright,0], [0, leftbottom], [0, lefttop], [topleft, 1], [topright, 1], [1, rightbottom], [1, 0] ]);
},
octagon: function(cell, x0, x1, x2, x3, opt) {
var bottomleft = opt.interpolate_a(x0, x1, opt.minV, opt.maxV);
var bottomright = opt.interpolate_b(x0, x1, opt.minV, opt.maxV);
var leftbottom = opt.interpolate_a(x0, x3, opt.minV, opt.maxV);
var lefttop = opt.interpolate_b(x0, x3, opt.minV, opt.maxV);
var topleft = opt.interpolate_a(x3, x2, opt.minV, opt.maxV);
var topright = opt.interpolate_b(x3, x2, opt.minV, opt.maxV);
var righttop = opt.interpolate_b(x1, x2, opt.minV, opt.maxV);
var rightbottom = opt.interpolate_a(x1, x2, opt.minV, opt.maxV);
if (opt.polygons_full) {
cell.edges.bl = {
path: [ [bottomleft, 0], [0, leftbottom] ],
move: {
x: -1,
y: 0,
enter: 'rb'
}
};
cell.edges.lt = {
path: [ [0, lefttop], [topleft, 1] ],
move: {
x: 0,
y: 1,
enter: 'bl'
}
};
cell.edges.tr = {
path: [ [topright, 1], [1, righttop] ],
move: {
x: 1,
y: 0,
enter: 'lt'
}
};
cell.edges.rb = {
path: [ [1, rightbottom], [bottomright, 0] ],
move: {
x: 0,
y: -1,
enter: 'tr'
}
};
}
if (opt.polygons)
cell.polygons.push([ [bottomleft, 0], [0, leftbottom], [0, lefttop], [topleft, 1], [topright, 1], [1, righttop], [1, rightbottom], [bottomright, 0] ]);
}
};
/*
* Compute isobands(s) for a scalar 2D field given a certain
* threshold and a bandwidth by applying the Marching Squares
* Algorithm. The function returns a list of path coordinates
* either for individual polygons within each grid cell, or the
* outline of connected polygons.
*/
function isoBands(input, minV, bandWidth, options) {
var i,
j,
settings,
useQuadTree = false,
tree = null,
root = null,
data = null,
cellGrid = null,
multiBand = false,
bw = [],
bandPolygons = [],
ret = [];
/* basic input validation */
if (!input) throw new Error('data is required');
if (minV === undefined || minV === null) throw new Error('lowerBound is required');
if (bandWidth === undefined || bandWidth === null) throw new Error('bandWidth is required');
if ((!!options) && (typeof options !== 'object')) throw new