Skip to content

Commit ac9ffbd

Browse files
committedOct 4, 2021
modernized histogram
1 parent cd4194e commit ac9ffbd

File tree

3 files changed

+146
-88
lines changed

3 files changed

+146
-88
lines changed
 

‎src/histogram/histogram.module.js

+40-38
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
import _ from 'underscore';
2-
import get from '../get';
3-
import utils from '../utils';
4-
import convertGeometry from '../convert-geometry';
5-
import intersectPolygon from '../intersect-polygon';
1+
/**
2+
* @prettier
3+
*/
4+
import _ from "underscore";
5+
import fastMin from "fast-min";
6+
import fastMax from "fast-max";
7+
8+
import get from "../get";
9+
import utils from "../utils";
10+
import wrap from "../wrap-func";
11+
import convertGeometry from "../convert-geometry";
12+
import intersectPolygon from "../intersect-polygon";
613

714
const getEqualIntervalBins = (values, numClasses) => {
8-
915
// get min and max values
10-
const minValue = _.min(values);
11-
const maxValue = _.max(values);
16+
const minValue = fastMin(values);
17+
const maxValue = fastMax(values);
1218

1319
// specify bins, bins represented as a list of [min, max] values
1420
// and are divided up based on number of classes
@@ -28,7 +34,8 @@ const getEqualIntervalBins = (values, numClasses) => {
2834
let binKey = `${bin[0]} - ${bin[1]}`;
2935
const firstValue = values[0];
3036

31-
while (firstValue > bin[1]) { // this is in case the first value isn't in the first bin
37+
while (firstValue > bin[1]) {
38+
// this is in case the first value isn't in the first bin
3239
binIndex += 1;
3340
bin = bins[binKey];
3441
binKey = `>${bin[0]} - ${bin[1]}`;
@@ -38,9 +45,11 @@ const getEqualIntervalBins = (values, numClasses) => {
3845
// add to results based on bins
3946
for (let i = 1; i < values.length; i++) {
4047
const value = values[i];
41-
if (value <= bin[1]) { // add to existing bin if its in the correct range
48+
if (value <= bin[1]) {
49+
// add to existing bin if its in the correct range
4250
results[binKey] += 1;
43-
} else { // otherwise keep searching for an appropriate bin until one is found
51+
} else {
52+
// otherwise keep searching for an appropriate bin until one is found
4453
while (value > bin[1]) {
4554
binIndex += 1;
4655
bin = bins[binIndex];
@@ -55,7 +64,6 @@ const getEqualIntervalBins = (values, numClasses) => {
5564
};
5665

5766
const getQuantileBins = (values, numClasses) => {
58-
5967
// get the number of values in each bin
6068
const valuesPerBin = values.length / numClasses;
6169

@@ -68,7 +76,8 @@ const getQuantileBins = (values, numClasses) => {
6876
for (let i = 1; i < values.length; i++) {
6977
if (numValuesInCurrentBin + 1 < valuesPerBin) {
7078
numValuesInCurrentBin += 1;
71-
} else { // if it is the last value, add it to the bin and start setting up for the next one
79+
} else {
80+
// if it is the last value, add it to the bin and start setting up for the next one
7281
const value = values[i];
7382
const binMax = value;
7483
numValuesInCurrentBin += 1;
@@ -89,7 +98,6 @@ const getQuantileBins = (values, numClasses) => {
8998
};
9099

91100
const getHistogram = (values, options = {}) => {
92-
93101
// pull out options, possible options are:
94102
// scaleType: measurement scale, options are: nominal, ratio
95103
// numClasses: number of classes/bins, only available for ratio data
@@ -108,14 +116,14 @@ const getHistogram = (values, options = {}) => {
108116

109117
// when working with nominal data, we simply create a new object attribute
110118
// for every new value, and increment for each additional value.
111-
if (scaleType === 'nominal') {
119+
if (scaleType === "nominal") {
112120
results = {};
113121
for (let i = 0; i < values.length; i++) {
114122
const value = values[i];
115123
if (results[value]) results[value] += 1;
116124
else results[value] = 1;
117125
}
118-
} else if (scaleType === 'ratio') {
126+
} else if (scaleType === "ratio") {
119127
results = {};
120128

121129
if (!numClasses) {
@@ -127,46 +135,41 @@ const getHistogram = (values, options = {}) => {
127135
// sort values to make binning more efficient
128136
values = values.sort((a, b) => a - b);
129137

130-
if (classType === 'equal-interval') {
138+
if (classType === "equal-interval") {
131139
results = getEqualIntervalBins(values, numClasses);
132-
} else if (classType === 'quantile') {
140+
} else if (classType === "quantile") {
133141
results = getQuantileBins(values, numClasses);
134142
} else {
135-
throw 'The classType provided is either not supported or incorrectly specified.';
143+
throw "The classType provided is either not supported or incorrectly specified.";
136144
}
137145
}
138146

139147
if (results) return results;
140-
else throw 'An unexpected error occurred while running the getHistogram function.';
148+
else throw "An unexpected error occurred while running the getHistogram function.";
141149
};
142150

143151
const getHistogramsForRaster = (georaster, geom, options) => {
152+
const { noDataValue } = georaster;
144153

145154
try {
155+
const calc = values => {
156+
return values.map(band => band.filter(value => value !== noDataValue)).map(band => getHistogram(band, options));
157+
};
158+
146159
if (geom === null || geom === undefined) {
147160
const flat = true;
148161
const values = get(georaster, null, flat);
149-
const { noDataValue } = georaster;
150-
151-
return values
152-
.map(band => band.filter(value => value !== noDataValue))
153-
.map(band => getHistogram(band, options));
154-
162+
return utils.callAfterResolveArgs(calc, values);
155163
} else if (utils.isBbox(geom)) {
156-
geom = convertGeometry('bbox', geom);
157-
const { noDataValue } = georaster;
164+
geom = convertGeometry("bbox", geom);
158165

159166
// grab array of values by band
160167
const flat = true;
161168
const values = get(georaster, geom, flat);
162169

163-
// run through histogram function
164-
return values
165-
.map(band => band.filter(value => value !== noDataValue))
166-
.map(band => getHistogram(band, options));
167-
170+
return utils.callAfterResolveArgs(calc, values);
168171
} else if (utils.isPolygon(geom)) {
169-
geom = convertGeometry('polygon', geom);
172+
geom = convertGeometry("polygon", geom);
170173
const { noDataValue } = georaster;
171174

172175
// grab array of values by band
@@ -183,14 +186,13 @@ const getHistogramsForRaster = (georaster, geom, options) => {
183186

184187
// run through histogram function
185188
return values.map(band => getHistogram(band, options));
186-
187189
} else {
188-
throw 'Only Bounding Box and Polygon geometries are currently supported.';
190+
throw "Only Bounding Box and Polygon geometries are currently supported.";
189191
}
190-
} catch(e) {
192+
} catch (e) {
191193
console.error(e);
192194
throw e;
193195
}
194196
};
195197

196-
export default getHistogramsForRaster;
198+
export default wrap(getHistogramsForRaster);

‎src/histogram/histogram.test.js

+101-48
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,84 @@
1-
import test from 'flug';
2-
import load from '../load';
3-
import histogram from './histogram.module';
1+
/**
2+
* @prettier
3+
*/
4+
import test from "flug";
5+
import { serve } from "srvd";
6+
import load from "../load";
7+
import histogram from "./histogram.module";
48

59
const ratioQuantileOptions = {
6-
scaleType: 'ratio',
10+
scaleType: "ratio",
711
numClasses: 7,
8-
classType: 'quantile',
12+
classType: "quantile"
913
};
1014

1115
const ratioEIOptions = {
12-
scaleType: 'ratio',
16+
scaleType: "ratio",
1317
numClasses: 7,
14-
classType: 'equal-interval',
18+
classType: "equal-interval"
1519
};
1620

1721
const ratioEIGeorasterResults = {
18-
'74 - 99.86': 140,
19-
'>99.86 - 125.71': 426,
20-
'>125.71 - 151.57': 305,
21-
'>151.57 - 177.43': 140,
22-
'>177.43 - 203.29': 62,
23-
'>203.29 - 229.14': 49,
24-
'>229.14 - 255': 78,
22+
"74 - 99.86": 140,
23+
">99.86 - 125.71": 426,
24+
">125.71 - 151.57": 305,
25+
">151.57 - 177.43": 140,
26+
">177.43 - 203.29": 62,
27+
">203.29 - 229.14": 49,
28+
">229.14 - 255": 78
2529
};
2630

2731
const ratioQuantileBboxResults = {
28-
'0 - 93.2': 31,
29-
'>93.2 - 272': 31,
30-
'>272 - 724': 31,
31-
'>724 - 1141.9': 31,
32-
'>1141.9 - 1700.6': 31,
33-
'>1700.6 - 2732.4': 31,
34-
'>2732.4 - 5166.7': 28,
32+
"0 - 93.2": 31,
33+
">93.2 - 272": 31,
34+
">272 - 724": 31,
35+
">724 - 1141.9": 31,
36+
">1141.9 - 1700.6": 31,
37+
">1700.6 - 2732.4": 31,
38+
">2732.4 - 5166.7": 28
3539
};
3640

3741
const ratioQuantilePolygonResults = {
38-
'0 - 129.3': 248,
39-
'>129.3 - 683.8': 248,
40-
'>683.8 - 1191': 248,
41-
'>1191 - 1948.7': 248,
42-
'>1948.7 - 2567.7': 248,
43-
'>2567.7 - 3483.9': 248,
44-
'>3483.9 - 7807.4': 246,
42+
"0 - 129.3": 248,
43+
">129.3 - 683.8": 248,
44+
">683.8 - 1191": 248,
45+
">1191 - 1948.7": 248,
46+
">1948.7 - 2567.7": 248,
47+
">2567.7 - 3483.9": 248,
48+
">3483.9 - 7807.4": 246
4549
};
4650

4751
const ratioEIPolygonResults = {
48-
'0 - 1115.34': 719,
49-
'>1115.34 - 2230.69': 373,
50-
'>2230.69 - 3346.03': 359,
51-
'>3346.03 - 4461.37': 170,
52-
'>4461.37 - 5576.71': 78,
53-
'>5576.71 - 6692.06': 25,
54-
'>6692.06 - 7807.4': 9,
52+
"0 - 1115.34": 719,
53+
">1115.34 - 2230.69": 373,
54+
">2230.69 - 3346.03": 359,
55+
">3346.03 - 4461.37": 170,
56+
">4461.37 - 5576.71": 78,
57+
">5576.71 - 6692.06": 25,
58+
">6692.06 - 7807.4": 9
5559
};
5660

57-
const url = 'http://localhost:3000/data/test.tiff';
58-
const urlSmallRaster = 'http://localhost:3000/data/example_4326.tif';
61+
const url = "http://localhost:3000/data/test.tiff";
62+
const urlSmallRaster = "http://localhost:3000/data/example_4326.tif";
5963

60-
const bbox = [80.63, 7.42, 84.21, 10.10];
64+
const bbox = [80.63, 7.42, 84.21, 10.1];
6165

62-
const polygon = [[
63-
[83.12255859375, 22.49225722008518], [82.96875, 21.57571893245848], [81.58447265624999, 1.207458730482642],
64-
[83.07861328125, 20.34462694382967], [83.8037109375, 19.497664168139053], [84.814453125, 19.766703551716976],
65-
[85.078125, 21.166483858206583], [86.044921875, 20.838277806058933], [86.98974609375, 22.49225722008518],
66-
[85.58349609375, 24.54712317973075], [84.6826171875, 23.36242859340884], [83.12255859375, 22.49225722008518]
67-
]];
66+
const polygon = [
67+
[
68+
[83.12255859375, 22.49225722008518],
69+
[82.96875, 21.57571893245848],
70+
[81.58447265624999, 1.207458730482642],
71+
[83.07861328125, 20.34462694382967],
72+
[83.8037109375, 19.497664168139053],
73+
[84.814453125, 19.766703551716976],
74+
[85.078125, 21.166483858206583],
75+
[86.044921875, 20.838277806058933],
76+
[86.98974609375, 22.49225722008518],
77+
[85.58349609375, 24.54712317973075],
78+
[84.6826171875, 23.36242859340884],
79+
[83.12255859375, 22.49225722008518]
80+
]
81+
];
6882

6983
const polygonGeojson = `{
7084
"type": "FeatureCollection",
@@ -87,7 +101,9 @@ const polygonGeojson = `{
87101
]
88102
}`;
89103

90-
test('Get Histogram (Ratio, Equal Interval) from GeoRaster', async ({ eq }) => {
104+
if (require.main === module) serve({ debug: true, port: 3000, wait: 15 });
105+
106+
test("(Legacy) Get Histogram (Ratio, Equal Interval) from GeoRaster", async ({ eq }) => {
91107
const georaster = await load(urlSmallRaster);
92108
const results = histogram(georaster, null, ratioEIOptions)[0];
93109
Object.keys(ratioEIGeorasterResults).forEach(key => {
@@ -97,7 +113,7 @@ test('Get Histogram (Ratio, Equal Interval) from GeoRaster', async ({ eq }) => {
97113
});
98114
});
99115

100-
test('Get Histogram (Ratio, Quantile) from Bounding Box', async ({ eq }) => {
116+
test("(Legacy) Get Histogram (Ratio, Quantile) from Bounding Box", async ({ eq }) => {
101117
const georaster = await load(url);
102118
const results = histogram(georaster, bbox, ratioQuantileOptions)[0];
103119
Object.keys(ratioQuantileBboxResults).forEach(key => {
@@ -107,7 +123,7 @@ test('Get Histogram (Ratio, Quantile) from Bounding Box', async ({ eq }) => {
107123
});
108124
});
109125

110-
test('Get Histogram (Ratio, Quantile) from Polygon', async ({ eq }) => {
126+
test("(Legacy) Get Histogram (Ratio, Quantile) from Polygon", async ({ eq }) => {
111127
const georaster = await load(url);
112128
const results = histogram(georaster, polygon, ratioQuantileOptions)[0];
113129
Object.keys(ratioQuantilePolygonResults).forEach(key => {
@@ -117,7 +133,7 @@ test('Get Histogram (Ratio, Quantile) from Polygon', async ({ eq }) => {
117133
});
118134
});
119135

120-
test('Get Histogram (Ratio, Equal Interval) from Polygon (GeoJSON)', async ({ eq }) => {
136+
test("(Legacy) Get Histogram (Ratio, Equal Interval) from Polygon (GeoJSON)", async ({ eq }) => {
121137
const georaster = await load(url);
122138
const results = histogram(georaster, polygonGeojson, ratioEIOptions)[0];
123139
Object.keys(ratioEIPolygonResults).forEach(key => {
@@ -126,3 +142,40 @@ test('Get Histogram (Ratio, Equal Interval) from Polygon (GeoJSON)', async ({ eq
126142
eq(value, expectedValue);
127143
});
128144
});
145+
146+
// MODERN
147+
test("(Modern) Get Histogram (Ratio, Equal Interval) from GeoRaster", async ({ eq }) => {
148+
const results = await histogram(urlSmallRaster, null, ratioEIOptions);
149+
Object.keys(ratioEIGeorasterResults).forEach(key => {
150+
const value = results[0][key];
151+
const expectedValue = ratioEIGeorasterResults[key];
152+
eq(value, expectedValue);
153+
});
154+
});
155+
156+
test("(Modern) Get Histogram (Ratio, Quantile) from Bounding Box", async ({ eq }) => {
157+
const results = await histogram(url, bbox, ratioQuantileOptions);
158+
Object.keys(ratioQuantileBboxResults).forEach(key => {
159+
const value = results[0][key];
160+
const expectedValue = ratioQuantileBboxResults[key];
161+
eq(value, expectedValue);
162+
});
163+
});
164+
165+
test("(Modern) Get Histogram (Ratio, Quantile) from Polygon", async ({ eq }) => {
166+
const results = await histogram(url, polygon, ratioQuantileOptions);
167+
Object.keys(ratioQuantilePolygonResults).forEach(key => {
168+
const value = results[0][key];
169+
const expectedValue = ratioQuantilePolygonResults[key];
170+
eq(value, expectedValue);
171+
});
172+
});
173+
174+
test("(Modern) Get Histogram (Ratio, Equal Interval) from Polygon (GeoJSON)", async ({ eq }) => {
175+
const results = await histogram(url, polygonGeojson, ratioEIOptions);
176+
Object.keys(ratioEIPolygonResults).forEach(key => {
177+
const value = results[0][key];
178+
const expectedValue = ratioEIPolygonResults[key];
179+
eq(value, expectedValue);
180+
});
181+
});

‎src/histogram/index.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import histogram from './histogram.module';
1+
/**
2+
* @prettier
3+
*/
4+
import histogram from "./histogram.module";
25

36
/**
47
* The histogram function takes a raster as an input and an optional geometry.
@@ -10,6 +13,6 @@ import histogram from './histogram.module';
1013
* @param {Object} [input=undefined] a geometry, which we'll use for clipping result
1114
* @returns {Object} array of histograms for each band
1215
* @example
13-
* var histograms = geoblaze.histogram(georaster, geometry);
16+
* var histograms = await geoblaze.histogram(georaster, geometry);
1417
*/
1518
export default histogram;

0 commit comments

Comments
 (0)
Please sign in to comment.