1818 */
1919package org .apache .sedona .common .raster ;
2020
21+ import static org .apache .sedona .common .utils .RasterUtils .getRasterScaleY ;
22+ import static org .apache .sedona .common .utils .RasterUtils .getRasterUpperLeftY ;
23+
2124import java .awt .image .WritableRaster ;
2225import java .math .BigDecimal ;
2326import java .math .RoundingMode ;
@@ -69,6 +72,11 @@ protected static List<Object> rasterize(
6972 rasterized = coverageFactory .create ("rasterized" , params .writableRaster , rasterExtent );
7073 }
7174
75+ // If original raster is bottom-up, flip the rasterized result vertically
76+ if (params .bottomUp ) {
77+ rasterized = RasterUtils .flipVerticallyGridSpace (rasterized );
78+ }
79+
7280 // Return results compatible with the original function
7381 List <Object > objects = new ArrayList <>();
7482 objects .add (params .writableRaster );
@@ -136,16 +144,22 @@ private static void rasterizePoint(
136144 int x = startX ;
137145 int y = startY ;
138146
139- for (double worldY = geomExtent .getMinY ();
140- worldY < geomExtent .getMaxY ();
141- worldY += params .scaleY , y ++ ) {
147+ for (double worldY = geomExtent .getMaxY ();
148+ worldY > geomExtent .getMinY ();
149+ worldY += params .scaleY , y -- ) {
142150 x = startX ;
143151 for (double worldX = geomExtent .getMinX ();
144152 worldX < geomExtent .getMaxX ();
145153 worldX += params .scaleX , x ++) {
146154
147- // Flip y-axis (since raster Y starts from top-left)
148- int yIndex = -y - 1 ;
155+ // Make zero indexed
156+ int yIndex = y - 1 ;
157+
158+ // Adjust for bottom-up rasters
159+ // Reverse the y index
160+ if (params .bottomUp ) {
161+ yIndex = params .writableRaster .getHeight () - 1 - yIndex ;
162+ }
149163
150164 // Create envelope for this pixel
151165 double cellMaxX = worldX + params .scaleX ;
@@ -182,9 +196,9 @@ private static void rasterizeLineString(
182196 }
183197
184198 double x0 = (start .x - params .upperLeftX ) / params .scaleX ;
185- double y0 = (params . upperLeftY - start . y ) / params .scaleY ;
199+ double y0 = (start . y - params . upperLeftY ) / params .scaleY ;
186200 double x1 = (end .x - params .upperLeftX ) / params .scaleX ;
187- double y1 = (params . upperLeftY - end . y ) / params .scaleY ;
201+ double y1 = (end . y - params . upperLeftY ) / params .scaleY ;
188202
189203 // Apply Bresenham for this segment
190204 drawLineBresenham (params , x0 , y0 , x1 , y1 , value , 0.2 );
@@ -221,6 +235,12 @@ private static void drawLineBresenham(
221235 int rasterX = (int ) (Math .floor (x ));
222236 int rasterY = (int ) (Math .floor (y ));
223237
238+ // Adjust for bottom-up rasters
239+ // Reverse the y index
240+ if (params .bottomUp ) {
241+ rasterY = params .writableRaster .getHeight () - 1 - rasterY ;
242+ }
243+
224244 // Only write if within raster bounds
225245 if (rasterX >= 0
226246 && rasterX < params .writableRaster .getWidth ()
@@ -338,9 +358,9 @@ static ReferencedEnvelope rasterizeGeomExtent(
338358
339359 // Using BigDecimal to avoid floating point errors
340360 double upperLeftX = metadata [0 ];
341- double upperLeftY = metadata [ 1 ] ;
361+ double upperLeftY = getRasterUpperLeftY ( metadata ) ;
342362 double scaleX = metadata [4 ];
343- double scaleY = metadata [ 5 ] ;
363+ double scaleY = getRasterScaleY ( metadata ) ;
344364
345365 // Compute the aligned min/max values
346366 double alignedMinX =
@@ -464,13 +484,14 @@ private static RasterizationParams calculateRasterizationParams(
464484 upperLeftY = geomExtent .getMaxY ();
465485 } else {
466486 upperLeftX = metadata [0 ];
467- upperLeftY = metadata [ 1 ] ;
487+ upperLeftY = getRasterUpperLeftY ( metadata ) ;
468488 }
469489
470490 WritableRaster writableRaster ;
471491 if (useGeometryExtent ) {
472492 int geomExtentWidth = (int ) (Math .round (geomExtent .getWidth () / metadata [4 ]));
473- int geomExtentHeight = (int ) (Math .round (geomExtent .getHeight () / -metadata [5 ]));
493+ int geomExtentHeight =
494+ (int ) (Math .round (geomExtent .getHeight () / -getRasterScaleY (metadata )));
474495
475496 writableRaster =
476497 RasterFactory .createBandedRaster (
@@ -483,19 +504,30 @@ private static RasterizationParams calculateRasterizationParams(
483504 RasterUtils .getDataTypeCode (pixelType ), rasterWidth , rasterHeight , 1 , null );
484505 }
485506
507+ boolean bottomUp = metadata [5 ] > 0 ;
508+
486509 return new RasterizationParams (
487- writableRaster , pixelType , metadata [4 ], -metadata [5 ], upperLeftX , upperLeftY );
510+ writableRaster ,
511+ pixelType ,
512+ metadata [4 ],
513+ getRasterScaleY (metadata ),
514+ upperLeftX ,
515+ upperLeftY ,
516+ bottomUp );
488517 }
489518
490519 private static void validateRasterMetadata (double [] rasterMetadata ) {
491520 if (rasterMetadata [4 ] < 0 ) {
492- throw new IllegalArgumentException ("ScaleX cannot be negative" );
493- }
494- if ( rasterMetadata [ 5 ] > 0 ) {
495- throw new IllegalArgumentException ( "ScaleY must be negative" );
521+ throw new IllegalArgumentException (
522+ String . format (
523+ "Invalid raster metadata: scaleX is negative (%.2f). Right-to-left rasters are not supported." ,
524+ rasterMetadata [ 4 ]) );
496525 }
497526 if (rasterMetadata [6 ] != 0 || rasterMetadata [7 ] != 0 ) {
498- throw new IllegalArgumentException ("SkewX and SkewY must be zero" );
527+ throw new IllegalArgumentException (
528+ String .format (
529+ "Invalid raster metadata: skewX is %.2f and skewY is %.2f. Both values must be zero." ,
530+ rasterMetadata [6 ], rasterMetadata [7 ]));
499531 }
500532 }
501533
@@ -506,20 +538,23 @@ private static class RasterizationParams {
506538 double scaleY ;
507539 double upperLeftX ;
508540 double upperLeftY ;
541+ boolean bottomUp ;
509542
510543 RasterizationParams (
511544 WritableRaster writableRaster ,
512545 String pixelType ,
513546 double scaleX ,
514547 double scaleY ,
515548 double upperLeftX ,
516- double upperLeftY ) {
549+ double upperLeftY ,
550+ boolean bottomUp ) {
517551 this .writableRaster = writableRaster ;
518552 this .pixelType = pixelType ;
519553 this .scaleX = scaleX ;
520554 this .scaleY = scaleY ;
521555 this .upperLeftX = upperLeftX ;
522556 this .upperLeftY = upperLeftY ;
557+ this .bottomUp = bottomUp ;
523558 }
524559 }
525560
@@ -602,15 +637,15 @@ private static Map<Double, TreeSet<Double>> computeScanlineIntersections(
602637 // Using BigDecimal to avoid floating point errors
603638 double yStart =
604639 Math .round (
605- (BigDecimal .valueOf (params . upperLeftY )
606- .subtract (BigDecimal .valueOf (worldP1 . y ))
640+ (BigDecimal .valueOf (worldP1 . y )
641+ .subtract (BigDecimal .valueOf (params . upperLeftY ))
607642 .divide (BigDecimal .valueOf (params .scaleY ), RoundingMode .CEILING ))
608643 .doubleValue ());
609644
610645 double yEnd =
611646 Math .round (
612- (BigDecimal .valueOf (params . upperLeftY )
613- .subtract (BigDecimal .valueOf (worldP2 . y ))
647+ (BigDecimal .valueOf (worldP2 . y )
648+ .subtract (BigDecimal .valueOf (params . upperLeftY ))
614649 .divide (BigDecimal .valueOf (params .scaleY ), RoundingMode .FLOOR ))
615650 .doubleValue ());
616651
@@ -619,7 +654,7 @@ private static Map<Double, TreeSet<Double>> computeScanlineIntersections(
619654 yStart = Math .min ((params .writableRaster .getHeight () - 0.5 ), Math .abs (yStart ) - 0.5 );
620655
621656 double p1X = (worldP1 .x - params .upperLeftX ) / params .scaleX ;
622- double p1Y = (params . upperLeftY - worldP1 . y ) / params .scaleY ;
657+ double p1Y = (worldP1 . y - params . upperLeftY ) / params .scaleY ;
623658
624659 if (worldP1 .x == worldP2 .x ) {
625660 // Vertical line case: directly set xIntercept. Avoid divide by zero error when
@@ -695,6 +730,13 @@ private static void fillPolygon(
695730
696731 for (Map .Entry <Integer , List <int []>> entry : scanlineFillRanges .entrySet ()) {
697732 int y = entry .getKey ();
733+
734+ // Adjust for bottom-up rasters
735+ // Reverse the y index
736+ if (params .bottomUp ) {
737+ y = params .writableRaster .getHeight () - 1 - y ;
738+ }
739+
698740 for (int [] range : entry .getValue ()) {
699741 if (range .length == 1 ) {
700742 params .writableRaster .setSample (range [0 ], y , 0 , value );
0 commit comments