Skip to content

Commit 036d669

Browse files
committed
MVP
1 parent fdf759b commit 036d669

File tree

7 files changed

+1057
-1
lines changed

7 files changed

+1057
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,4 @@ dist
107107
.DS_Store
108108

109109
tmp/
110+
test-data/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# geowarp
2-
Super Low-Level Raster Reprojection and Resampling Library
2+
> Super Low-Level Raster Reprojection and Resampling Library
33
44
# install
55
```bash

geowarp.js

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
const forEach = (nums, no_data, cb) => {
2+
const len = nums.length;
3+
if (no_data) {
4+
for (let i = 0; i < len; i++) {
5+
const n = nums[i];
6+
if (n !== no_data) cb(n);
7+
}
8+
} else {
9+
for (let i = 0; i < len; i++) {
10+
cb(nums[i]);
11+
}
12+
}
13+
};
14+
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+
};
22+
23+
const mean = (nums, in_no_data, out_no_data) => {
24+
let running_sum = 0;
25+
let count = 0;
26+
forEach(nums, in_no_data, n => {
27+
count++;
28+
running_sum += n;
29+
});
30+
return count === 0 ? out_no_data : running_sum / count;
31+
};
32+
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+
}
56+
};
57+
58+
const mode = (nums, no_data) => {
59+
switch (nums.length) {
60+
case 0:
61+
return undefined;
62+
default:
63+
const counts = {};
64+
if (no_data) {
65+
for (let i = 0; i < nums.length; i++) {
66+
const n = nums[i];
67+
if (n !== no_data) {
68+
if (n in counts) counts[n].count++;
69+
else counts[n] = { n, count: 1 };
70+
}
71+
}
72+
} else {
73+
for (let i = 0; i < nums.length; i++) {
74+
const n = nums[i];
75+
if (n in counts) counts[n].count++;
76+
else counts[n] = { n, count: 1 };
77+
}
78+
}
79+
const items = Object.values(counts);
80+
const count = items.sort((a, b) => Math.sign(b.count - a.count))[0].count;
81+
return items.filter(it => it.count === count).map(it => it.n);
82+
}
83+
};
84+
85+
const geowarp = ({
86+
debug_level = 0,
87+
reproject, // equivalent of proj4(source, target).inverse()
88+
in_data,
89+
in_bbox,
90+
in_srs,
91+
in_height,
92+
in_width,
93+
in_no_data,
94+
out_bbox,
95+
out_srs,
96+
out_width = 256,
97+
out_height = 256,
98+
out_no_data = null,
99+
method = "median",
100+
round = false, // whether to round output
101+
}) => {
102+
if (debug_level) console.log("[geowarp] starting");
103+
104+
const sameSRS = in_srs === out_srs;
105+
106+
if (!sameSRS && typeof reproject !== "function") {
107+
throw new Error("[geowarp] you must specify a reproject function");
108+
}
109+
110+
if (!in_height) throw new Error("[geowarp] you must provide in_height");
111+
if (!in_width) throw new Error("[geowarp] you must provide in_width");
112+
113+
const num_bands = in_data.length;
114+
if (debug_level) console.log("[geowarp] number of bands in source data:", num_bands);
115+
116+
const [in_xmin, in_ymin, in_xmax, in_ymax] = in_bbox;
117+
118+
const in_pixel_height = (in_ymax - in_ymin) / in_height;
119+
const in_pixel_width = (in_xmax - in_xmin) / in_width;
120+
if (debug_level) console.log("[geowarp] pixel height of source data:", in_pixel_height);
121+
if (debug_level) console.log("[geowarp] pixel width of source data:", in_pixel_width);
122+
123+
const [out_xmin, out_ymin, out_xmax, out_ymax] = out_bbox;
124+
125+
const out_pixel_height = (out_ymax - out_ymin) / out_height;
126+
const out_pixel_width = (out_xmax - out_xmin) / out_width;
127+
128+
// iterate over pixels in the out box
129+
const rows = [];
130+
let top, left, bottom, right;
131+
bottom = out_ymax;
132+
for (let r = 0; r < out_height; r++) {
133+
const row = [];
134+
top = bottom;
135+
bottom = top - out_pixel_height;
136+
right = out_xmin;
137+
for (let c = 0; c < out_width; c++) {
138+
left = right;
139+
right = left + out_pixel_width;
140+
// top, left, bottom, right is the sample area in the coordinate system of the output
141+
142+
// convert to bbox of input coordinate system
143+
const bbox_in_srs = sameSRS ? [left, bottom, right, top] : [...reproject([left, bottom]), ...reproject([right, top])];
144+
// console.log({bbox});
145+
const [xmin_in_srs, ymin_in_srs, xmax_in_srs, ymax_in_srs] = bbox_in_srs;
146+
147+
// convert bbox in input srs to raster pixels
148+
const leftInRasterPixels = (xmin_in_srs - in_xmin) / in_pixel_width;
149+
const rightInRasterPixels = (xmax_in_srs - in_xmin) / in_pixel_width;
150+
const topInRasterPixels = (in_ymax - ymax_in_srs) / in_pixel_height;
151+
const bottomInRasterPixels = (in_ymax - ymin_in_srs) / in_pixel_height;
152+
// console.log({xmin_in_srs, in_xmin, leftInRasterPixels, rightInRasterPixels, topInRasterPixels, bottomInRasterPixels});
153+
154+
const pixel = [];
155+
const leftSample = Math.round(leftInRasterPixels);
156+
const rightSample = Math.round(rightInRasterPixels);
157+
const topSample = Math.round(topInRasterPixels);
158+
const bottomSample = Math.round(bottomInRasterPixels);
159+
for (let b = 0; b < num_bands; b++) {
160+
const band = in_data[b];
161+
const values = [];
162+
for (let y = topSample; y <= bottomSample; y++) {
163+
const start = y * in_width;
164+
for (let x = leftSample; x <= rightSample; x++) {
165+
// assuming flattened data by band
166+
const value = band[start + x];
167+
values.push(value);
168+
}
169+
}
170+
171+
let pixelBandValue = null;
172+
if (method === "max") {
173+
pixelBandValue = max(values, in_no_data, out_no_data);
174+
} else if (method === "mean") {
175+
pixelBandValue = mean(values, in_no_data, out_no_data);
176+
} else if (method === "median") {
177+
pixelBandValue = median(values, in_no_data, out_no_data);
178+
} else if (method === "min") {
179+
pixelBandValue = min(values, in_no_data, out_no_data);
180+
} else if (method.startsWith("mode")) {
181+
const modes = mode(values);
182+
const len = modes.length;
183+
if (len === 1) {
184+
pixelBandValue = modes[0];
185+
} else {
186+
if (method === "mode") {
187+
pixelBandValue = modes[0];
188+
} else if (method === "mode-max") {
189+
pixelBandValue = max(values);
190+
} else if (method === "mode-mean") {
191+
pixelBandValue = mean(values);
192+
} else if (method === "mode-median") {
193+
pixelBandValue = median(values);
194+
} else if (method === "mode-min") {
195+
pixelBandValue = min(values);
196+
}
197+
}
198+
}
199+
if (round) pixelBandValue = Math.round(pixelBandValue);
200+
pixel.push(pixelBandValue);
201+
}
202+
row.push(pixel);
203+
}
204+
rows.push(row);
205+
}
206+
207+
if (debug_level) console.log("[geowarp] finishing");
208+
return { data: rows };
209+
};
210+
211+
if (typeof module === "object") module.exports = geowarp;
212+
if (typeof window === "object") window.geowarp = geowarp;
213+
if (typeof self === "object") self.geowarp = geowarp;

0 commit comments

Comments
 (0)