UNPKG

marchingsquares

Version:

MarchingSquaresJS - An implementation of the Marching Squares algorithm featuring Isocontour and Isoband computation.

1,683 lines (1,458 loc) 72.6 kB
/*! * 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