@@ -6,7 +6,7 @@ const Geotransform = require("geoaffine/Geotransform.js");
66const getDepth = require ( "get-depth" ) ;
77const getTheoreticalMax = require ( "typed-array-ranges/get-max" ) ;
88const getTheoreticalMin = require ( "typed-array-ranges/get-min" ) ;
9- const fasterMedian = require ( "faster-median" ) ;
9+ const calcMedian = require ( "mediana" ) . calculate ;
1010const reprojectBoundingBox = require ( "bbox-fns/reproject.js" ) ;
1111const reprojectGeoJSON = require ( "reproject-geojson/pluggable" ) ;
1212const { turbocharge } = require ( "proj-turbo" ) ;
@@ -18,6 +18,8 @@ const xdim = require("xdim");
1818
1919const clamp = ( n , min , max ) => ( n < min ? min : n > max ? max : n ) ;
2020
21+ const isInvalid = n => n === undefined || n === null || n !== n ;
22+
2123const scaleInteger = ( n , r ) => {
2224 const n2 = Math . round ( n * r ) ;
2325 return [ n2 , n2 / n , n / n2 ] ;
@@ -41,7 +43,7 @@ const forEach = (nums, no_data, cb) => {
4143 if ( no_data ) {
4244 for ( let i = 0 ; i < len ; i ++ ) {
4345 const n = nums [ i ] ;
44- if ( n !== no_data ) cb ( n ) ;
46+ if ( no_data . indexOf ( n ) === - 1 ) cb ( n ) ;
4547 }
4648 } else {
4749 for ( let i = 0 ; i < len ; i ++ ) {
@@ -62,12 +64,11 @@ const mean = (nums, in_no_data) => {
6264
6365const mode = ( nums , no_data ) => {
6466 if ( nums . length === 0 ) return undefined ;
65-
6667 const counts = { } ;
6768 if ( no_data ) {
6869 for ( let i = 0 ; i < nums . length ; i ++ ) {
6970 const n = nums [ i ] ;
70- if ( n !== no_data ) {
71+ if ( typeof n === "number" && n === n && no_data . indexOf ( n ) === - 1 ) {
7172 if ( n in counts ) counts [ n ] . count ++ ;
7273 else counts [ n ] = { n, count : 1 } ;
7374 }
@@ -121,7 +122,7 @@ const geowarp = function geowarp({
121122 in_pixel_height, // optional, automatically calculated from in_bbox
122123 in_pixel_width, // optional, automatically calculated from in_bbox
123124 in_width,
124- in_no_data,
125+ in_no_data, // optional, supports one number or an array of unique no data values
125126 out_array_types, // array of constructor names passed to internal call to xdim's prepareData function
126127 out_bands, // array of bands to keep and order, default is keeping all the bands in same order
127128 out_data, // single or multi-dimensional array that geowarp will fill in with the output
@@ -240,6 +241,15 @@ const geowarp = function geowarp({
240241 if ( round && typeof out_no_data === "number" ) out_no_data = Math . round ( out_no_data ) ;
241242 // if (out_no_data === null && out_no_data_strategy === "keep") out_no_data = in_no_data;
242243
244+ if ( Array . isArray ( in_no_data ) === false ) {
245+ if ( "in_no_data" in arguments [ 0 ] ) {
246+ in_no_data = [ in_no_data ] ;
247+ } else {
248+ in_no_data = [ ] ;
249+ }
250+ }
251+ const primary_in_no_data = in_no_data [ 0 ] ;
252+
243253 // processing step after we have read the raw pixel values
244254 let process ;
245255 if ( expr ) {
@@ -258,14 +268,14 @@ const geowarp = function geowarp({
258268 process = ( { pixel } ) =>
259269 out_bands_to_read_bands . map ( iband => {
260270 const value = pixel [ iband ] ;
261- return value === undefined || value === in_no_data ? out_no_data : Math . round ( value ) ;
271+ return isInvalid ( value ) || in_no_data . includes ( value ) ? out_no_data : Math . round ( value ) ;
262272 } ) ;
263273 } else {
264274 // without rounding
265275 process = ( { pixel } ) =>
266276 out_bands_to_read_bands . map ( iband => {
267277 const value = pixel [ iband ] ;
268- return value === undefined || value === in_no_data ? out_no_data : value ;
278+ return isInvalid ( value ) || in_no_data . includes ( value ) ? out_no_data : value ;
269279 } ) ;
270280 }
271281 }
@@ -594,9 +604,9 @@ const geowarp = function geowarp({
594604
595605 const should_skip =
596606 skip_no_data_strategy === "any"
597- ? px => px . includes ( undefined ) || px . includes ( in_no_data )
607+ ? px => px . some ( n => isInvalid ( n ) || in_no_data . includes ( n ) )
598608 : skip_no_data_strategy === "all"
599- ? px => px . every ( n => n === in_no_data )
609+ ? px => px . every ( n => isInvalid ( n ) || in_no_data . includes ( n ) )
600610 : ( ) => false ;
601611
602612 if ( method === "vectorize" ) {
@@ -703,7 +713,7 @@ const geowarp = function geowarp({
703713
704714 if ( x_in_raster_pixels < 0 || y_in_raster_pixels < 0 || x_in_raster_pixels >= in_width || y_in_raster_pixels >= in_height ) {
705715 // through reprojection, we can sometimes find ourselves just across the edge
706- raw_values = new Array ( read_bands . length ) . fill ( in_no_data ) ;
716+ raw_values = new Array ( read_bands . length ) . fill ( primary_in_no_data ) ;
707717 } else {
708718 raw_values = selectPixel ( {
709719 row : y_in_raster_pixels ,
@@ -758,31 +768,33 @@ const geowarp = function geowarp({
758768 const topWeight = top === bottom ? 0.5 : bottom - yInRasterPixels ;
759769 const bottomWeight = top === bottom ? 0.5 : yInRasterPixels - top ;
760770
761- const leftInvalid = left < 0 || left >= in_width ;
762- const rightInvalid = right < 0 || right >= in_width ;
763- const topInvalid = top < 0 || top >= in_height ;
764- const bottomInvalid = bottom < 0 || bottom >= in_height ;
771+ const leftOutside = left < 0 || left >= in_width ;
772+ const rightOutside = right < 0 || right >= in_width ;
773+ const topOutside = top < 0 || top >= in_height ;
774+ const bottomOutside = bottom < 0 || bottom >= in_height ;
765775
766- const upperLeftInvalid = topInvalid || leftInvalid ;
767- const upperRightInvalid = topInvalid || rightInvalid ;
768- const lowerLeftInvalid = bottomInvalid || leftInvalid ;
769- const lowerRightInvalid = bottomInvalid || rightInvalid ;
776+ const upperleftOutside = topOutside || leftOutside ;
777+ const upperRightOutside = topOutside || rightOutside ;
778+ const lowerleftOutside = bottomOutside || leftOutside ;
779+ const lowerRightOutside = bottomOutside || rightOutside ;
770780
771781 const raw_values = new Array ( ) ;
772782 for ( let i = 0 ; i < read_bands . length ; i ++ ) {
773783 const read_band = read_bands [ i ] ;
774784
775- const upperLeftValue = upperLeftInvalid ? in_no_data : select ( { point : { band : read_band , row : top , column : left } } ) . value ;
776- const upperRightValue = upperRightInvalid ? in_no_data : select ( { point : { band : read_band , row : top , column : right } } ) . value ;
777- const lowerLeftValue = lowerLeftInvalid ? in_no_data : select ( { point : { band : read_band , row : bottom , column : left } } ) . value ;
778- const lowerRightValue = lowerRightInvalid ? in_no_data : select ( { point : { band : read_band , row : bottom , column : right } } ) . value ;
785+ const upperLeftValue = upperleftOutside ? primary_in_no_data : select ( { point : { band : read_band , row : top , column : left } } ) . value ;
786+ const upperRightValue = upperRightOutside ? primary_in_no_data : select ( { point : { band : read_band , row : top , column : right } } ) . value ;
787+ const lowerLeftValue = lowerleftOutside ? primary_in_no_data : select ( { point : { band : read_band , row : bottom , column : left } } ) . value ;
788+ const lowerRightValue = lowerRightOutside ? primary_in_no_data : select ( { point : { band : read_band , row : bottom , column : right } } ) . value ;
779789
780790 let topValue ;
781- if ( ( upperLeftValue === undefined || upperLeftValue === in_no_data ) && ( upperRightValue === undefined || upperRightValue === in_no_data ) ) {
791+ const upperLeftInvalid = isInvalid ( upperLeftValue ) || in_no_data . includes ( upperLeftValue ) ;
792+ const upperRightInvalid = isInvalid ( upperRightValue ) || in_no_data . includes ( upperRightValue ) ;
793+ if ( upperLeftInvalid && upperRightInvalid ) {
782794 // keep topValue undefined
783- } else if ( upperLeftValue === undefined || upperLeftValue === in_no_data ) {
795+ } else if ( upperLeftInvalid ) {
784796 topValue = upperRightValue ;
785- } else if ( upperRightValue === undefined || upperRightValue === in_no_data ) {
797+ } else if ( upperRightInvalid ) {
786798 topValue = upperLeftValue ;
787799 } else if ( upperLeftValue === upperRightValue ) {
788800 // because the upper-left and upper-right values are the same, no weighting is necessary
@@ -792,11 +804,13 @@ const geowarp = function geowarp({
792804 }
793805
794806 let bottomValue ;
795- if ( ( lowerLeftValue === undefined || lowerLeftValue === in_no_data ) && ( lowerRightValue === undefined || lowerRightValue === in_no_data ) ) {
807+ const lowerLeftInvalid = isInvalid ( lowerLeftValue ) || in_no_data . includes ( lowerLeftValue ) ;
808+ const lowerRightInvalid = isInvalid ( lowerRightValue ) || in_no_data . includes ( lowerRightValue ) ;
809+ if ( lowerLeftInvalid && lowerRightInvalid ) {
796810 // keep bottom value undefined
797- } else if ( lowerLeftValue === undefined || lowerLeftValue === in_no_data ) {
811+ } else if ( lowerLeftInvalid ) {
798812 bottomValue = lowerRightValue ;
799- } else if ( lowerRightValue === undefined || lowerRightValue === in_no_data ) {
813+ } else if ( lowerRightInvalid ) {
800814 bottomValue = lowerLeftValue ;
801815 } else if ( lowerLeftValue === lowerRightValue ) {
802816 // because the lower-left and lower-right values are the same, no weighting is necessary
@@ -807,7 +821,7 @@ const geowarp = function geowarp({
807821
808822 let value ;
809823 if ( topValue === undefined && bottomValue === undefined ) {
810- value = in_no_data ;
824+ value = primary_in_no_data ;
811825 } else if ( topValue === undefined ) {
812826 value = bottomValue ;
813827 } else if ( bottomValue === undefined ) {
@@ -827,27 +841,29 @@ const geowarp = function geowarp({
827841 }
828842 }
829843 } else {
844+ // Q: why don't we pass no_data to the following statistical methods (e.g. fastMax)?
845+ // A: we are already filtering out invalid and no-data values beforehand
830846 let calc ;
831847 if ( typeof method === "function" ) {
832- calc = values => ( values . length === 0 ? in_no_data : method ( { values } ) ) ;
848+ calc = values => method ( { values } ) ;
833849 } else if ( method === "max" ) {
834- calc = values => ( values . length === 0 ? in_no_data : fastMax ( values , { no_data : in_no_data , theoretical_max } ) ) ;
850+ calc = values => fastMax ( values , { theoretical_max } ) ;
835851 } else if ( method === "mean" ) {
836- calc = values => ( values . length === 0 ? in_no_data : mean ( values , in_no_data ) ) ;
852+ calc = values => mean ( values ) ;
837853 } else if ( method === "median" ) {
838- calc = values => ( values . length === 0 ? in_no_data : fasterMedian ( { nums : values , no_data : in_no_data } ) ) ;
854+ calc = values => calcMedian ( values ) ;
839855 } else if ( method === "min" ) {
840- calc = values => ( values . length === 0 ? in_no_data : fastMin ( values , { no_data : in_no_data , theoretical_min } ) ) ;
856+ calc = values => fastMin ( values , { theoretical_min } ) ;
841857 } else if ( method === "mode" ) {
842- calc = values => ( values . length === 0 ? in_no_data : mode ( values ) [ 0 ] ) ;
858+ calc = values => mode ( values ) [ 0 ] ;
843859 } else if ( method === "mode-max" ) {
844- calc = values => ( values . length === 0 ? in_no_data : fastMax ( mode ( values ) ) ) ;
860+ calc = values => fastMax ( mode ( values ) ) ;
845861 } else if ( method === "mode-mean" ) {
846- calc = values => ( values . length === 0 ? in_no_data : mean ( mode ( values ) ) ) ;
862+ calc = values => mean ( mode ( values ) ) ;
847863 } else if ( method === "mode-median" ) {
848- calc = values => ( values . length === 0 ? in_no_data : fasterMedian ( { nums : mode ( values ) } ) ) ;
864+ calc = values => calcMedian ( mode ( values ) ) ;
849865 } else if ( method === "mode-min" ) {
850- calc = values => ( values . length === 0 ? in_no_data : fastMin ( mode ( values ) ) ) ;
866+ calc = values => fastMin ( mode ( values ) ) ;
851867 } else {
852868 throw new Error ( `[geowarp] unknown method "${ method } "` ) ;
853869 }
@@ -923,8 +939,12 @@ const geowarp = function geowarp({
923939 column : [ leftSample , Math . max ( leftSample , rightSample - 1 ) ]
924940 }
925941 } ) ;
926- const valid_values = values . filter ( v => v !== undefined && v !== in_no_data ) ;
927- raw_values . push ( calc ( valid_values ) ) ;
942+ const valid_values = values . filter ( v => typeof v === "number" && v === v && in_no_data . indexOf ( v ) === - 1 ) ;
943+ if ( valid_values . length === 0 ) {
944+ raw_values . push ( primary_in_no_data ) ;
945+ } else {
946+ raw_values . push ( calc ( valid_values ) ) ;
947+ }
928948 }
929949 }
930950
0 commit comments