@@ -6,6 +6,8 @@ const getTheoreticalMin = require("typed-array-ranges/get-min");
66const fasterMedian = require ( "faster-median" ) ;
77const xdim = require ( "xdim" ) ;
88
9+ const eq = ( a , b ) => JSON . stringify ( a ) === JSON . stringify ( b ) ;
10+
911const uniq = arr => Array . from ( new Set ( arr ) ) . sort ( ( a , b ) => b - a ) ;
1012
1113const range = ct => new Array ( ct ) . fill ( 0 ) . map ( ( _ , i ) => i ) ;
@@ -55,7 +57,7 @@ const mode = (nums, no_data) => {
5557 return undefined ;
5658 default :
5759 const counts = { } ;
58- if ( no_data ) {
60+ if ( no_data ) { q
5961 for ( let i = 0 ; i < nums . length ; i ++ ) {
6062 const n = nums [ i ] ;
6163 if ( n !== no_data ) {
@@ -76,11 +78,28 @@ const mode = (nums, no_data) => {
7678 }
7779} ;
7880
79- const geowarp = ( {
81+ // taken from reproject-bbox
82+ // because don't want proj4 dependency
83+ const reprojectBoundingBox = ( { bbox, forward } ) => {
84+ const [ xmin , ymin , xmax , ymax ] = bbox ;
85+
86+ const topleft = forward ( [ xmin , ymax ] ) ;
87+ const topright = forward ( [ xmax , ymax ] ) ;
88+ const bottomleft = forward ( [ xmin , ymin ] ) ;
89+ const bottomright = forward ( [ xmax , ymin ] ) ;
90+
91+ const corners = [ topleft , topright , bottomleft , bottomright ] ;
92+
93+ const xs = corners . map ( corner => corner [ 0 ] ) ;
94+ const ys = corners . map ( corner => corner [ 1 ] ) ;
95+
96+ return [ Math . min ( ...xs ) , Math . min ( ...ys ) , Math . max ( ...xs ) , Math . max ( ...ys ) ] ;
97+ } ;
98+
99+ const geowarp = function geowarp ( {
80100 debug_level = 0 ,
81- reproject, // equivalent of proj4(source, target).inverse()
82101 in_data,
83- in_bbox,
102+ in_bbox = undefined ,
84103 in_layout = "[band][row,column]" ,
85104 in_srs,
86105 in_height,
@@ -93,7 +112,7 @@ const geowarp = ({
93112 out_pixel_depth, // number of output bands
94113 out_pixel_height, // optional, automatically calculated from out_bbox
95114 out_pixel_width, // optional, automatically calculated from out_bbox
96- out_bbox,
115+ out_bbox = null ,
97116 out_layout,
98117 out_srs,
99118 out_width = 256 ,
@@ -104,14 +123,27 @@ const geowarp = ({
104123 expr = undefined , // band expression function
105124 round = false , // whether to round output
106125 theoretical_min, // minimum theoretical value (e.g., 0 for unsigned integer arrays)
107- theoretical_max // maximum values (e.g., 255 for 8-bit unsigned integer arrays)
108- } ) => {
126+ theoretical_max, // maximum values (e.g., 255 for 8-bit unsigned integer arrays),
127+ inverse, // function to reproject [x, y] point from out_srs back to in_srs
128+ forward // function to reproject [x, y] point from in_srs to out_srs
129+ } ) {
109130 if ( debug_level >= 1 ) console . log ( "[geowarp] starting" ) ;
110131
111132 const same_srs = in_srs === out_srs ;
112133 if ( debug_level >= 1 ) console . log ( "[geowarp] input and output srs are the same:" , same_srs ) ;
113134
114- if ( ! same_srs && typeof reproject !== "function" ) {
135+ // support for deprecated alias of inverse
136+ inverse ??= arguments [ 0 ] . reproject ;
137+
138+ if ( ! same_srs ) {
139+ if ( ! in_bbox ) throw new Error ( "[geowarp] can't reproject without in_bbox" ) ;
140+ if ( ! out_bbox ) {
141+ if ( forward ) out_bbox = reprojectBoundingBox ( { bbox : in_bbox , forward } ) ;
142+ else throw new Error ( "[geowarp] must specify out_bbox or forward" ) ;
143+ }
144+ }
145+
146+ if ( ! same_srs && typeof inverse !== "function" ) {
115147 throw new Error ( "[geowarp] you must specify a reproject function" ) ;
116148 }
117149
@@ -161,7 +193,7 @@ const geowarp = ({
161193 out_pixel_depth ??= out_bands ?. length ?? read_bands ?. length ?? in_pixel_depth ;
162194
163195 // just resizing an image without reprojection
164- if ( same_srs && ! in_bbox && ! out_bbox ) {
196+ if ( same_srs && eq ( in_bbox , out_bbox ) ) {
165197 out_srs = in_srs = null ;
166198 in_bbox = [ 0 , 0 , in_width , in_height ] ;
167199 out_bbox = [ 0 , 0 , out_width , out_height ] ;
@@ -190,8 +222,8 @@ const geowarp = ({
190222 try {
191223 const data_constructor = in_data [ 0 ] . constructor . name ;
192224 if ( debug_level >= 1 ) console . log ( "[geowarp] data_constructor:" , data_constructor ) ;
193- if ( theoretical_min === undefined ) theoretical_min = getTheoreticalMin ( data_constructor ) ;
194- if ( theoretical_max === undefined ) theoretical_max = getTheoreticalMax ( data_constructor ) ;
225+ theoretical_min ?? = getTheoreticalMin ( data_constructor ) ;
226+ theoretical_max ?? = getTheoreticalMax ( data_constructor ) ;
195227 if ( debug_level >= 1 ) console . log ( "[geowarp] theoretical_min:" , theoretical_min ) ;
196228 if ( debug_level >= 1 ) console . log ( "[geowarp] theoretical_max:" , theoretical_max ) ;
197229 } catch ( error ) {
@@ -214,7 +246,12 @@ const geowarp = ({
214246 column : out_width
215247 } ;
216248
217- const { data : out_data } = xdim . prepareData ( { layout : out_layout , sizes : out_sizes } ) ;
249+ // can we later on skip inserting no data pixels
250+ const { data : out_data } = xdim . prepareData ( {
251+ fill : out_no_data ,
252+ layout : out_layout ,
253+ sizes : out_sizes
254+ } ) ;
218255
219256 const update = xdim . prepareUpdate ( { data : out_data , layout : out_layout , sizes : out_sizes } ) ;
220257
@@ -234,7 +271,7 @@ const geowarp = ({
234271 for ( let c = 0 ; c < out_width ; c ++ ) {
235272 const x = out_xmin + out_pixel_width * c ;
236273 const pt_out_srs = [ x , y ] ;
237- const [ x_in_srs , y_in_srs ] = same_srs ? pt_out_srs : reproject ( pt_out_srs ) ;
274+ const [ x_in_srs , y_in_srs ] = same_srs ? pt_out_srs : inverse ( pt_out_srs ) ;
238275 const xInRasterPixels = Math . round ( ( x_in_srs - in_xmin ) / in_pixel_width ) ;
239276 const yInRasterPixels = Math . round ( ( in_ymax - y_in_srs ) / in_pixel_height ) ;
240277 let pixel = [ ] ;
@@ -266,7 +303,7 @@ const geowarp = ({
266303 for ( let c = 0 ; c < out_width ; c ++ ) {
267304 const x = out_xmin + out_pixel_width * c ;
268305 const pt_out_srs = [ x , y ] ;
269- const [ x_in_srs , y_in_srs ] = same_srs ? pt_out_srs : reproject ( pt_out_srs ) ;
306+ const [ x_in_srs , y_in_srs ] = same_srs ? pt_out_srs : inverse ( pt_out_srs ) ;
270307
271308 const xInRasterPixels = ( x_in_srs - in_xmin ) / in_pixel_width ;
272309 const yInRasterPixels = ( in_ymax - y_in_srs ) / in_pixel_height ;
@@ -347,7 +384,7 @@ const geowarp = ({
347384 // top, left, bottom, right is the sample area in the coordinate system of the output
348385
349386 // convert to bbox of input coordinate system
350- const bbox_in_srs = same_srs ? [ left , bottom , right , top ] : [ ...reproject ( [ left , bottom ] ) , ...reproject ( [ right , top ] ) ] ;
387+ const bbox_in_srs = same_srs ? [ left , bottom , right , top ] : [ ...inverse ( [ left , bottom ] ) , ...inverse ( [ right , top ] ) ] ;
351388 if ( debug_level >= 3 ) console . log ( "[geowarp] bbox_in_srs:" , bbox_in_srs ) ;
352389 const [ xmin_in_srs , ymin_in_srs , xmax_in_srs , ymax_in_srs ] = bbox_in_srs ;
353390
@@ -361,68 +398,88 @@ const geowarp = ({
361398 const bottomInRasterPixels = ( in_ymax - ymin_in_srs ) / in_pixel_height ;
362399 if ( debug_level >= 4 ) console . log ( "[geowarp] bottomInRasterPixels:" , bottomInRasterPixels ) ;
363400
364- const leftSample = Math . round ( leftInRasterPixels ) ;
365- const rightSample = Math . round ( rightInRasterPixels ) ;
366- const topSample = Math . round ( topInRasterPixels ) ;
367- const bottomSample = Math . round ( bottomInRasterPixels ) ;
368- let pixel = [ ] ;
369- for ( let i = 0 ; i < read_bands . length ; i ++ ) {
370- const read_band = read_bands [ i ] ;
371- const { data : values } = xdim . clip ( {
372- data : in_data ,
373- flat : true ,
374- layout : in_layout ,
375- sizes : in_sizes ,
376- rect : {
377- band : [ read_band , read_band ] ,
378- row : [ topSample , Math . max ( topSample , bottomSample - 1 ) ] ,
379- column : [ leftSample , Math . max ( leftSample , rightSample - 1 ) ]
380- }
381- } ) ;
401+ // round and make sure leftSample isn't less than zero
402+ let leftSample = Math . round ( leftInRasterPixels ) ;
403+ let rightSample = Math . round ( rightInRasterPixels ) ;
404+ let topSample = Math . round ( topInRasterPixels ) ;
405+ let bottomSample = Math . round ( bottomInRasterPixels ) ;
382406
383- let pixelBandValue = null ;
384- if ( typeof method === "function" ) {
385- pixelBandValue = method ( { values } ) ;
386- } else if ( method === "max" ) {
387- pixelBandValue = max ( { nums : values , in_no_data, out_no_data, theoretical_max : undefined } ) ;
388- } else if ( method === "mean" ) {
389- pixelBandValue = mean ( values , in_no_data , out_no_data ) ;
390- } else if ( method === "median" ) {
391- pixelBandValue = median ( { nums : values , in_no_data, out_no_data } ) ;
392- } else if ( method === "min" ) {
393- pixelBandValue = min ( { nums : values , in_no_data, out_no_data, theoretical_min : undefined } ) ;
394- } else if ( method . startsWith ( "mode" ) ) {
395- const modes = mode ( values ) ;
396- const len = modes . length ;
397- if ( len === 1 ) {
398- pixelBandValue = modes [ 0 ] ;
399- } else {
400- if ( method === "mode" ) {
407+ let pixel = [ ] ;
408+ if ( leftSample >= in_width || rightSample < 0 || topSample < 0 || bottomSample >= in_height ) {
409+ pixel = new Array ( read_bands . length ) . fill ( in_no_data ) ;
410+ } else {
411+ // clamp edges to prevent clipping outside bounds
412+ leftSample = Math . max ( 0 , leftSample ) ;
413+ rightSample = Math . min ( rightSample , in_width ) ;
414+ topSample = Math . max ( 0 , topSample ) ;
415+ bottomSample = Math . min ( bottomSample , in_height ) ;
416+
417+ for ( let i = 0 ; i < read_bands . length ; i ++ ) {
418+ const read_band = read_bands [ i ] ;
419+ const { data : values } = xdim . clip ( {
420+ data : in_data ,
421+ flat : true ,
422+ layout : in_layout ,
423+ sizes : in_sizes ,
424+ rect : {
425+ band : [ read_band , read_band ] ,
426+ row : [ topSample , Math . max ( topSample , bottomSample - 1 ) ] ,
427+ column : [ leftSample , Math . max ( leftSample , rightSample - 1 ) ]
428+ }
429+ } ) ;
430+
431+ let pixelBandValue = null ;
432+ if ( typeof method === "function" ) {
433+ pixelBandValue = method ( { values } ) ;
434+ } else if ( method === "max" ) {
435+ pixelBandValue = max ( { nums : values , in_no_data, out_no_data, theoretical_max : undefined } ) ;
436+ } else if ( method === "mean" ) {
437+ pixelBandValue = mean ( values , in_no_data , out_no_data ) ;
438+ } else if ( method === "median" ) {
439+ pixelBandValue = median ( { nums : values , in_no_data, out_no_data } ) ;
440+ } else if ( method === "min" ) {
441+ pixelBandValue = min ( { nums : values , in_no_data, out_no_data, theoretical_min : undefined } ) ;
442+ } else if ( method . startsWith ( "mode" ) ) {
443+ const modes = mode ( values ) ;
444+ const len = modes . length ;
445+ if ( len === 1 ) {
401446 pixelBandValue = modes [ 0 ] ;
402- } else if ( method === "mode-max" ) {
403- pixelBandValue = max ( { nums : values } ) ;
404- } else if ( method === "mode-mean" ) {
405- pixelBandValue = mean ( values ) ;
406- } else if ( method === "mode-median" ) {
407- pixelBandValue = median ( { nums : values } ) ;
408- } else if ( method === "mode-min" ) {
409- pixelBandValue = min ( { nums : values } ) ;
447+ } else {
448+ if ( method === "mode" ) {
449+ pixelBandValue = modes [ 0 ] ;
450+ } else if ( method === "mode-max" ) {
451+ pixelBandValue = max ( { nums : values } ) ;
452+ } else if ( method === "mode-mean" ) {
453+ pixelBandValue = mean ( values ) ;
454+ } else if ( method === "mode-median" ) {
455+ pixelBandValue = median ( { nums : values } ) ;
456+ } else if ( method === "mode-min" ) {
457+ pixelBandValue = min ( { nums : values } ) ;
458+ }
410459 }
460+ } else {
461+ throw new Error ( `[geowarp] unknown method "${ method } "` ) ;
411462 }
412- } else {
413- throw new Error ( `[geowarp] unknown method " ${ method } "` ) ;
463+ if ( round ) pixelBandValue = Math . round ( pixelBandValue ) ;
464+ pixel . push ( pixelBandValue ) ;
414465 }
415- if ( round ) pixelBandValue = Math . round ( pixelBandValue ) ;
416- pixel . push ( pixelBandValue ) ;
417466 }
467+
418468 if ( process ) pixel = process ( { pixel } ) ;
419469 insert ( { row : r , column : c , pixel } ) ;
420470 }
421471 }
422472 }
423473
424474 if ( debug_level >= 1 ) console . log ( "[geowarp] finishing" ) ;
425- return { data : out_data , out_bands, out_layout, read_bands } ;
475+ return {
476+ data : out_data ,
477+ out_bands,
478+ out_layout,
479+ out_pixel_height,
480+ out_pixel_width,
481+ read_bands
482+ } ;
426483} ;
427484
428485if ( typeof module === "object" ) module . exports = geowarp ;
0 commit comments