Skip to content

Commit eb73c54

Browse files
committed
sped up max, min, and median resampling
1 parent 036d669 commit eb73c54

File tree

5 files changed

+192
-59
lines changed

5 files changed

+192
-59
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,19 @@ const result = geowarp({
6666

6767
// round output pixel values to closest integer
6868
// do this if you will convert your output to a PNG or JPG
69-
round: true
69+
round: true,
70+
71+
// optional
72+
// the lowest possible pixel value considering the bit-depth of the data
73+
// this is used to speed up the min and mode-min resampling
74+
// if in_data is an array of typed arrays, this will be automatically calculated
75+
theoretical_min: 0,
76+
77+
// optional
78+
// the highest possible pixel value considering the bit-depth of the data
79+
// this is used to speed up the max and mode-max resampling
80+
// if in_data is an array of typed arrays, this will be automatically calculated
81+
theoretical_max: 255
7082
});
7183

7284
// result.data is a 3-dimensional array of pixel values broken down by row then column the band

geowarp.js

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
const fastMax = require("fast-max");
2+
const fastMin = require("fast-min");
3+
const getTheoreticalMax = require("typed-array-ranges/get-max");
4+
const getTheoreticalMin = require("typed-array-ranges/get-min");
5+
const fasterMedian = require("faster-median");
6+
17
const forEach = (nums, no_data, cb) => {
28
const len = nums.length;
39
if (no_data) {
@@ -12,12 +18,14 @@ const forEach = (nums, no_data, cb) => {
1218
}
1319
};
1420

15-
const max = (nums, in_no_data, out_no_data) => {
16-
let result = -Infinity;
17-
forEach(nums, in_no_data, n => {
18-
if (n > result) result = n;
19-
});
20-
return result === -Infinity ? out_no_data : result;
21+
const median = ({ nums, in_no_data, out_no_data }) => {
22+
const result = fasterMedian({ nums, no_data: in_no_data });
23+
return result === undefined ? out_no_data : result;
24+
};
25+
26+
const max = ({ nums, in_no_data, out_no_data, theoretical_max }) => {
27+
const result = fastMax(nums, { no_data: in_no_data, theoretical_max });
28+
return result === undefined ? out_no_data : result;
2129
};
2230

2331
const mean = (nums, in_no_data, out_no_data) => {
@@ -30,29 +38,9 @@ const mean = (nums, in_no_data, out_no_data) => {
3038
return count === 0 ? out_no_data : running_sum / count;
3139
};
3240

33-
const min = (nums, in_no_data, out_no_data) => {
34-
let result = Infinity;
35-
forEach(nums, in_no_data, n => {
36-
if (n < result) result = n;
37-
});
38-
return result === Infinity ? out_no_data : result;
39-
};
40-
41-
const median = (nums, in_no_data, out_no_data) => {
42-
nums = nums.filter(n => n !== in_no_data).sort();
43-
switch (nums.length) {
44-
case 0:
45-
return out_no_data;
46-
case 1:
47-
return nums[0];
48-
default:
49-
const mid = nums.length / 2;
50-
if (nums.length % 2 === 0) {
51-
return (nums[mid - 1] + nums[mid]) / 2;
52-
} else {
53-
return nums[Math.floor(mid)];
54-
}
55-
}
41+
const min = ({ nums, in_no_data, out_no_data, theoretical_min }) => {
42+
const result = fastMin(nums, { no_data: in_no_data, theoretical_min });
43+
return result === undefined ? out_no_data : result;
5644
};
5745

5846
const mode = (nums, no_data) => {
@@ -98,10 +86,13 @@ const geowarp = ({
9886
out_no_data = null,
9987
method = "median",
10088
round = false, // whether to round output
89+
theoretical_min, // minimum theoretical value (e.g., 0 for unsigned integer arrays)
90+
theoretical_max, // maximum values (e.g., 255 for 8-bit unsigned integer arrays)
10191
}) => {
10292
if (debug_level) console.log("[geowarp] starting");
10393

10494
const sameSRS = in_srs === out_srs;
95+
if (debug_level) console.log("[geowarp] input and output srs are the same:", sameSRS);
10596

10697
if (!sameSRS && typeof reproject !== "function") {
10798
throw new Error("[geowarp] you must specify a reproject function");
@@ -113,6 +104,7 @@ const geowarp = ({
113104
const num_bands = in_data.length;
114105
if (debug_level) console.log("[geowarp] number of bands in source data:", num_bands);
115106

107+
if (debug_level) console.log("[geowarp] method:", method);
116108
const [in_xmin, in_ymin, in_xmax, in_ymax] = in_bbox;
117109

118110
const in_pixel_height = (in_ymax - in_ymin) / in_height;
@@ -121,9 +113,30 @@ const geowarp = ({
121113
if (debug_level) console.log("[geowarp] pixel width of source data:", in_pixel_width);
122114

123115
const [out_xmin, out_ymin, out_xmax, out_ymax] = out_bbox;
116+
if (debug_level) console.log("[geowarp] out_xmin:", out_xmin);
117+
if (debug_level) console.log("[geowarp] out_ymin:", out_ymin);
118+
if (debug_level) console.log("[geowarp] out_xmax:", out_xmax);
119+
if (debug_level) console.log("[geowarp] out_ymax:", out_ymax);
124120

125121
const out_pixel_height = (out_ymax - out_ymin) / out_height;
126122
const out_pixel_width = (out_xmax - out_xmin) / out_width;
123+
if (debug_level) console.log("[geowarp] out_pixel_height:", out_pixel_height);
124+
if (debug_level) console.log("[geowarp] out_pixel_width:", out_pixel_width);
125+
126+
if (theoretical_min === undefined || theoretical_max === undefined) {
127+
try {
128+
const data_constructor = in_data[0].constructor.name;
129+
if (debug_level) console.log("[geowarp] data_constructor:", data_constructor);
130+
if (theoretical_min === undefined) theoretical_min = getTheoreticalMin(data_constructor);
131+
if (theoretical_max === undefined) theoretical_max = getTheoreticalMax(data_constructor);
132+
if (debug_level) console.log("[geowarp] theoretical_min:", theoretical_min);
133+
if (debug_level) console.log("[geowarp] theoretical_max:", theoretical_max);
134+
} catch (error) {
135+
// we want to log an error if it happens
136+
// even if we don't strictly need it to succeed
137+
console.error(error);
138+
}
139+
}
127140

128141
// iterate over pixels in the out box
129142
const rows = [];
@@ -141,7 +154,7 @@ const geowarp = ({
141154

142155
// convert to bbox of input coordinate system
143156
const bbox_in_srs = sameSRS ? [left, bottom, right, top] : [...reproject([left, bottom]), ...reproject([right, top])];
144-
// console.log({bbox});
157+
if (debug_level >= 3) console.log("bbox_in_srs:", bbox_in_srs);
145158
const [xmin_in_srs, ymin_in_srs, xmax_in_srs, ymax_in_srs] = bbox_in_srs;
146159

147160
// convert bbox in input srs to raster pixels
@@ -158,25 +171,27 @@ const geowarp = ({
158171
const bottomSample = Math.round(bottomInRasterPixels);
159172
for (let b = 0; b < num_bands; b++) {
160173
const band = in_data[b];
174+
// const values = new band.constructor((bottomSample - topSample + 1) * (rightSample - leftSample + 1));
161175
const values = [];
162-
for (let y = topSample; y <= bottomSample; y++) {
176+
for (let y = topSample, i = 0; y <= bottomSample; y++) {
163177
const start = y * in_width;
164178
for (let x = leftSample; x <= rightSample; x++) {
165179
// assuming flattened data by band
166-
const value = band[start + x];
167-
values.push(value);
180+
// values[i++] = band[start + x];
181+
values.push(band[start + x]);
168182
}
169183
}
184+
// console.log("values:", JSON.stringify(values));
170185

171186
let pixelBandValue = null;
172187
if (method === "max") {
173-
pixelBandValue = max(values, in_no_data, out_no_data);
188+
pixelBandValue = max({ nums: values, in_no_data, out_no_data, theoretical_max: undefined });
174189
} else if (method === "mean") {
175190
pixelBandValue = mean(values, in_no_data, out_no_data);
176191
} else if (method === "median") {
177-
pixelBandValue = median(values, in_no_data, out_no_data);
192+
pixelBandValue = median({ nums: values, in_no_data, out_no_data });
178193
} else if (method === "min") {
179-
pixelBandValue = min(values, in_no_data, out_no_data);
194+
pixelBandValue = min({ nums: values, in_no_data, out_no_data, theoretical_min: undefined });
180195
} else if (method.startsWith("mode")) {
181196
const modes = mode(values);
182197
const len = modes.length;
@@ -186,13 +201,13 @@ const geowarp = ({
186201
if (method === "mode") {
187202
pixelBandValue = modes[0];
188203
} else if (method === "mode-max") {
189-
pixelBandValue = max(values);
204+
pixelBandValue = max({ nums: values });
190205
} else if (method === "mode-mean") {
191206
pixelBandValue = mean(values);
192207
} else if (method === "mode-median") {
193-
pixelBandValue = median(values);
208+
pixelBandValue = median({ nums: values });
194209
} else if (method === "mode-min") {
195-
pixelBandValue = min(values);
210+
pixelBandValue = min({ nums: values });
196211
}
197212
}
198213
}

package-lock.json

Lines changed: 65 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,17 @@
3939
"devDependencies": {
4040
"@mapbox/tilebelt": "^1.0.2",
4141
"fast-counter": "*",
42-
"flug": "^1.0.3",
42+
"flug": "^1.1.0",
4343
"geotiff": "^1.0.4",
4444
"geotiff-read-bbox": "*",
4545
"pngjs": "^6.0.0",
4646
"proj4-fully-loaded": "^0.0.2",
4747
"reproject-bbox": "^0.0.1"
48+
},
49+
"dependencies": {
50+
"fast-max": "^0.3.0",
51+
"fast-min": "^0.2.0",
52+
"faster-median": "^0.0.0",
53+
"typed-array-ranges": "^0.0.0"
4854
}
4955
}

0 commit comments

Comments
 (0)