@@ -2,6 +2,7 @@ const { booleanIntersects, intersect, polygon } = require("bbox-fns");
22const dufour_peyton_intersection = require ( "dufour-peyton-intersection" ) ;
33const fastMax = require ( "fast-max" ) ;
44const fastMin = require ( "fast-min" ) ;
5+ const Geotransform = require ( "geoaffine/Geotransform.js" ) ;
56const getDepth = require ( "get-depth" ) ;
67const getTheoreticalMax = require ( "typed-array-ranges/get-max" ) ;
78const getTheoreticalMin = require ( "typed-array-ranges/get-min" ) ;
@@ -112,6 +113,7 @@ const geowarp = function geowarp({
112113 debug_level = 0 ,
113114 in_data,
114115 in_bbox = undefined ,
116+ in_geotransform = undefined , // 6-parameter geotransform, only necessary when in_data is skewed or rotated
115117 in_layout = "[band][row,column]" ,
116118 in_srs,
117119 in_height,
@@ -285,6 +287,13 @@ const geowarp = function geowarp({
285287 if ( debug_level >= 1 ) console . log ( "[geowarp] pixel height of source data:" , in_pixel_height ) ;
286288 if ( debug_level >= 1 ) console . log ( "[geowarp] pixel width of source data:" , in_pixel_width ) ;
287289
290+ in_geotransform ??= [ in_xmin , in_pixel_width , 0 , in_ymax , 0 , - 1 * in_pixel_height ] ;
291+
292+ const { forward : in_img_pt_to_srs_pt , inverse : in_srs_pt_to_in_img_pt } = Geotransform ( in_geotransform ) ;
293+
294+ // convert point in output srs to image pixel coordinate in input image
295+ const out_srs_pt_to_in_img_pt = same_srs ? in_srs_pt_to_in_img_pt : pt => in_srs_pt_to_in_img_pt ( inv ( pt ) ) ;
296+
288297 const [ out_xmin , out_ymin , out_xmax , out_ymax ] = out_bbox ;
289298 if ( debug_level >= 1 ) console . log ( "[geowarp] out_xmin:" , out_xmin ) ;
290299 if ( debug_level >= 1 ) console . log ( "[geowarp] out_ymin:" , out_ymin ) ;
@@ -304,6 +313,12 @@ const geowarp = function geowarp({
304313 const half_out_sample_height = out_sample_height / 2 ;
305314 const half_out_sample_width = out_sample_width / 2 ;
306315
316+ // const out_geotransform = [out_xmin, out_pixel_width, 0, out_ymax, 0, -1 * out_pixel_height];
317+ // const { forward: out_img_pt_to_srs_pt, inverse: out_srs_pt_to_img_pt } = Geotransform(out_geotransform);
318+
319+ const in_img_pt_to_out_srs_pt = same_srs ? in_img_pt_to_srs_pt : pt => fwd ( in_img_pt_to_srs_pt ( pt ) ) ;
320+ // const in_img_pt_to_out_img_pt = same_srs ? pt => out_srs_pt_to_img_pt(in_img_pt_to_srs_pts(pt)) : pt => out_srs_pt_to_img_pt(fwd(in_img_pt_to_srs_pt(pt)));
321+
307322 if ( theoretical_min === undefined || theoretical_max === undefined ) {
308323 try {
309324 const data_constructor = in_data [ 0 ] . constructor . name ;
@@ -590,61 +605,56 @@ const geowarp = function geowarp({
590605 [ left , bottom , right , top ] = cutline && cutline_strategy !== "inside" ? intersect ( out_bbox_in_srs , cutline_bbox_in_srs ) : out_bbox_in_srs ;
591606
592607 if ( ( left < in_xmax && bottom < in_ymax && right > in_xmin ) || top < in_ymin ) {
593- const in_row_start = Math . floor ( ( in_ymax - top ) / in_pixel_height ) ;
594- const in_row_start_clamped = clamp ( in_row_start , 0 , in_height - 1 ) ;
595- const in_row_end = Math . min ( Math . floor ( ( in_ymax - bottom ) / in_pixel_height ) , in_height - 1 ) ;
596- const in_row_end_clamped = clamp ( in_row_end , 0 , in_height - 1 ) ;
597-
598- const in_column_start = Math . floor ( ( left - in_xmin ) / in_pixel_width ) ;
599- const in_column_start_clamped = clamp ( in_column_start , 0 , in_width - 1 ) ;
600- const in_column_end = Math . min ( Math . floor ( ( right - in_xmin ) / in_pixel_width ) , in_width - 1 ) ;
601- const in_column_end_clamped = clamp ( in_column_end , 0 , in_width - 1 ) ;
602-
603- let pixel_ymin = in_ymax - in_row_start_clamped * in_pixel_height ;
604- for ( let r = in_row_start_clamped ; r <= in_row_end_clamped ; r ++ ) {
605- // if (clear_process_cache) clear_process_cache();
606- const pixel_ymax = pixel_ymin ;
607- pixel_ymin = pixel_ymax - in_pixel_height ;
608- // clear_forward_cache(); // don't want cache to get too large, so just cache each row
609- for ( let c = in_column_start_clamped ; c <= in_column_end_clamped ; c ++ ) {
610- const raw_values = read_bands . map ( band => select ( { point : { band, row : r , column : c } } ) . value ) ;
611-
612- if ( should_skip ( raw_values ) ) continue ;
613-
614- const pixel_xmin = in_xmin + c * in_pixel_width ;
615- const pixel_xmax = pixel_xmin + in_pixel_width ;
616-
617- const pixel_bbox = [ pixel_xmin , pixel_ymin , pixel_xmax , pixel_ymax ] ;
618-
619- // convert pixel to a rectangle polygon in srs of input data
620- const rect = polygon ( pixel_bbox ) ;
621-
622- // reproject pixel rectangle from input to output srs
623- const pixel_geometry_in_out_srs = same_srs ? rect : reprojectGeoJSON ( rect , { reproject : fwd } ) ;
624-
625- const intersect_options = {
626- debug : false ,
627- raster_bbox : out_bbox ,
628- raster_height : out_height_in_samples ,
629- raster_width : out_width_in_samples ,
630- geometry : pixel_geometry_in_out_srs
631- } ;
632-
633- // apply band math expression, no-data mapping, and rounding when applicable
634- const pixel = process ( { pixel : raw_values } ) ;
635-
636- if ( cutline ) {
637- intersect_options . per_pixel = ( { row, column } ) => {
638- if ( segments_by_row [ row ] . some ( ( [ start , end ] ) => column >= start && column <= end ) ) {
639- insert_sample ( { raw : raw_values , pixel, row, column } ) ;
640- }
641- } ;
642- } else {
643- intersect_options . per_pixel = ( { row, column } ) => {
644- insert_sample ( { raw : raw_values , pixel, row, column } ) ;
608+ const out_bbox_in_input_image_coords = reprojectBoundingBox ( out_bbox_in_srs , in_srs_pt_to_in_img_pt ) ;
609+
610+ // need to double check intersection in image space in case of rotation/skew
611+ if ( booleanIntersects ( out_bbox_in_input_image_coords , [ 0 , 0 , in_width , in_height ] ) ) {
612+ // snap to pixel array inidices
613+ const [ in_column_start , in_row_start , in_column_end , in_row_end ] = out_bbox_in_input_image_coords . map ( n => Math . floor ( n ) ) ;
614+ const in_row_start_clamped = clamp ( in_row_start , 0 , in_height - 1 ) ;
615+ const in_row_end_clamped = clamp ( in_row_end , 0 , in_height - 1 ) ;
616+ const in_column_start_clamped = clamp ( in_column_start , 0 , in_width - 1 ) ;
617+ const in_column_end_clamped = clamp ( in_column_end , 0 , in_width - 1 ) ;
618+
619+ for ( let r = in_row_start_clamped ; r <= in_row_end_clamped ; r ++ ) {
620+ // if (clear_process_cache) clear_process_cache();
621+ // clear_forward_cache(); // don't want cache to get too large, so just cache each row
622+ for ( let c = in_column_start_clamped ; c <= in_column_end_clamped ; c ++ ) {
623+ const raw_values = read_bands . map ( band => select ( { point : { band, row : r , column : c } } ) . value ) ;
624+
625+ if ( should_skip ( raw_values ) ) continue ;
626+
627+ const rect = polygon ( [ c , r , c + 1 , r + 1 ] ) ;
628+
629+ // to-do: reproject to [I, J] (output image point) because
630+ // intersection algorithm assumes an unskewed space
631+ // we'll only have to do this if we want to support rotated/skewed output
632+ const pixel_geometry_in_out_srs = reprojectGeoJSON ( rect , { reproject : in_img_pt_to_out_srs_pt } ) ;
633+
634+ const intersect_options = {
635+ debug : false ,
636+ raster_bbox : out_bbox ,
637+ raster_height : out_height_in_samples ,
638+ raster_width : out_width_in_samples ,
639+ geometry : pixel_geometry_in_out_srs
645640 } ;
641+
642+ // apply band math expression, no-data mapping, and rounding when applicable
643+ const pixel = process ( { pixel : raw_values } ) ;
644+
645+ if ( cutline ) {
646+ intersect_options . per_pixel = ( { row, column } ) => {
647+ if ( segments_by_row [ row ] . some ( ( [ start , end ] ) => column >= start && column <= end ) ) {
648+ insert_sample ( { raw : raw_values , pixel, row, column } ) ;
649+ }
650+ } ;
651+ } else {
652+ intersect_options . per_pixel = ( { row, column } ) => {
653+ insert_sample ( { raw : raw_values , pixel, row, column } ) ;
654+ } ;
655+ }
656+ dufour_peyton_intersection . calculate ( intersect_options ) ;
646657 }
647- dufour_peyton_intersection . calculate ( intersect_options ) ;
648658 }
649659 }
650660 }
@@ -662,9 +672,7 @@ const geowarp = function geowarp({
662672 const x = out_xmin + c * out_sample_width + half_out_sample_width ;
663673 const pt_out_srs = [ x , y ] ;
664674 const pt_in_srs = same_srs ? pt_out_srs : inv ( pt_out_srs ) ;
665- const [ x_in_srs , y_in_srs ] = pt_in_srs ;
666- const x_in_raster_pixels = Math . floor ( ( x_in_srs - in_xmin ) / in_pixel_width ) ;
667- const y_in_raster_pixels = Math . floor ( ( in_ymax - y_in_srs ) / in_pixel_height ) ;
675+ const [ x_in_raster_pixels , y_in_raster_pixels ] = in_srs_pt_to_in_img_pt ( pt_in_srs ) . map ( n => Math . floor ( n ) ) ;
668676
669677 let raw_values = [ ] ;
670678
@@ -708,10 +716,8 @@ const geowarp = function geowarp({
708716 for ( let c = cstart ; c <= cend ; c ++ ) {
709717 const x = out_xmin + c * out_sample_width + half_out_sample_width ;
710718 const pt_out_srs = [ x , y ] ;
711- const [ x_in_srs , y_in_srs ] = same_srs ? pt_out_srs : inv ( pt_out_srs ) ;
712-
713- const xInRasterPixels = ( x_in_srs - in_xmin ) / in_pixel_width ;
714- const yInRasterPixels = ( in_ymax - y_in_srs ) / in_pixel_height ;
719+ const pt_in_srs = same_srs ? pt_out_srs : inv ( pt_out_srs ) ;
720+ const [ xInRasterPixels , yInRasterPixels ] = in_srs_pt_to_in_img_pt ( pt_in_srs ) ;
715721
716722 const left = Math . floor ( xInRasterPixels ) ;
717723 const right = Math . ceil ( xInRasterPixels ) ;
@@ -835,19 +841,18 @@ const geowarp = function geowarp({
835841 right = left + out_sample_width ;
836842 // top, left, bottom, right is the sample area in the coordinate system of the output
837843
838- // convert to bbox of input coordinate system
839- const bbox_in_srs = same_srs ? [ left , bottom , right , top ] : reprojectBoundingBox ( [ left , bottom , right , top ] , inv , { density : 0 } ) ;
840- if ( debug_level >= 3 ) console . log ( "[geowarp] bbox_in_srs:" , bbox_in_srs ) ;
841- const [ xmin_in_srs , ymin_in_srs , xmax_in_srs , ymax_in_srs ] = bbox_in_srs ;
844+ // convert bbox in output srs to image px of input
845+ // combing srs reprojection and srs-to-image mapping, ensures that bounding box corners
846+ // are reprojected fully before calculating containing bbox
847+ // (prevents drift in increasing bbox twice if image is warped)
848+ const [ leftInRasterPixels , topInRasterPixels , rightInRasterPixels , bottomInRasterPixels ] = reprojectBoundingBox (
849+ [ left , bottom , right , top ] ,
850+ out_srs_pt_to_in_img_pt
851+ ) ;
842852
843- // convert bbox in input srs to raster pixels
844- const leftInRasterPixels = ( xmin_in_srs - in_xmin ) / in_pixel_width ;
845853 if ( debug_level >= 4 ) console . log ( "[geowarp] leftInRasterPixels:" , leftInRasterPixels ) ;
846- const rightInRasterPixels = ( xmax_in_srs - in_xmin ) / in_pixel_width ;
847854 if ( debug_level >= 4 ) console . log ( "[geowarp] rightInRasterPixels:" , rightInRasterPixels ) ;
848- const topInRasterPixels = ( in_ymax - ymax_in_srs ) / in_pixel_height ;
849855 if ( debug_level >= 4 ) console . log ( "[geowarp] topInRasterPixels:" , topInRasterPixels ) ;
850- const bottomInRasterPixels = ( in_ymax - ymin_in_srs ) / in_pixel_height ;
851856 if ( debug_level >= 4 ) console . log ( "[geowarp] bottomInRasterPixels:" , bottomInRasterPixels ) ;
852857
853858 let leftSample = Math . round ( leftInRasterPixels ) ;
0 commit comments