-
Notifications
You must be signed in to change notification settings - Fork 28
/
band-arithmetic.module.js
133 lines (111 loc) · 4.05 KB
/
band-arithmetic.module.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import _ from 'underscore';
import parseGeoraster from 'georaster';
import { parse } from 'mathjs';
import utils from '../utils';
const { listVariables } = utils;
const regexMultiCharacter = /[A-z]{2}/g;
const containsNoDataValue = (bandValues, noDataValue) => {
const numBandValues = bandValues.length;
for (let i = 0; i < numBandValues; i++) {
if (bandValues[i] === noDataValue) return true;
}
return false;
};
const isLeafNode = node => !node.op;
const parseAST = (ast, numBands) => {
return new Function(...listVariables(numBands), `return ${parseNode(ast)}`);
};
const parseNode = node => {
const leftNode = node.args[0];
const rightNode = node.args[1];
const operation = node.op;
let leftHandSide;
if (leftNode.content) { // if the node represents parentheses, it will be an object with a single property "content" which contains a node
leftHandSide = `(${parseNode(leftNode.content)}`;
} else if (isLeafNode(leftNode)) {
leftHandSide = `(${leftNode.value || leftNode.name}`;
} else {
leftHandSide = `(${parseNode(leftNode)}`;
}
let rightHandSide;
if (rightNode.content) { // if the node represents parentheses, it will be an object with a single property "content" which contains a node
rightHandSide = `${parseNode(rightNode.content)})`;
} else if (isLeafNode(rightNode)) {
rightHandSide = `${rightNode.value || rightNode.name})`;
} else {
rightHandSide = `${parseNode(rightNode)})`;
}
return `${leftHandSide} ${operation} ${rightHandSide}`;
};
const getBandRows = (bands, index) => {
// using a for loop here instead of map leads to a significant performance improvement
const bandRows = [];
for (let i = 0; i < bands.length; i++) {
bandRows.push(bands[i][index]);
}
return bandRows;
};
const getBandValues = (bandRows, index) => {
// using a for loop here instead of map leads to a significant performance improvement
const bandValues = [];
for (let i = 0; i < bandRows.length; i++) {
bandValues.push(bandRows[i][index]);
}
return bandValues;
};
// pre-parse arithmetic string to catch limitations with arithmetic operations
// before attempting to compute
const arithmeticError = arithmetic => {
if (arithmetic.match(regexMultiCharacter)) {
return ('Geoblaze does not currently support implicit multiplication between variables. Please use the multiplication (*) symbol for these operations.');
}
};
const bandArithmetic = (georaster, arithmetic) => {
return new Promise((resolve, reject) => {
if (georaster.values.length < 2) {
return reject(new Error('Band arithmetic is not available for this raster. Please make sure you are using a multi-band raster.'));
}
const parseError = arithmeticError(arithmetic);
if (parseError) return reject(new Error(parseError));
try {
const bands = georaster.values;
const noDataValue = georaster.noDataValue;
const values = [];
const numRows = bands[0].length;
const ast = parse(arithmetic.toLowerCase());
const arithmeticFunction = parseAST(ast, bands.length);
for (let i = 0; i < numRows; i++) {
const bandRows = getBandRows(bands, i);
const row = [];
const numValues = bandRows[0].length;
for (let j = 0; j < numValues; j++) {
const bandValues = getBandValues(bandRows, j);
if (containsNoDataValue(bandValues, noDataValue)) {
row.push(noDataValue);
} else {
const value = arithmeticFunction(...bandValues);
if (value === Infinity || value === -Infinity || isNaN(value)) {
row.push(noDataValue);
} else {
row.push(value);
}
}
}
values.push(row);
}
const metadata = _.pick(georaster, ...[
'noDataValue',
'projection',
'xmin',
'ymax',
'pixelWidth',
'pixelHeight'
]);
return parseGeoraster([values], metadata).then(georaster => resolve(georaster));
} catch(e) {
console.error(e);
return reject(e);
}
});
};
export default bandArithmetic;