This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new 811889461 [SEDONA-629] Change RS Functions to return struct instead of 
array type (#1524)
811889461 is described below

commit 811889461e97bf8bae4654d1d527f8da100e40fb
Author: Feng Zhang <[email protected]>
AuthorDate: Thu Jul 18 08:51:36 2024 -0700

    [SEDONA-629] Change RS Functions to return struct instead of array type 
(#1524)
    
    * [SEDONA-629] Change RS Functions to return struct instead of array type
    
    * refactor RS_SummaryStatsAll
    
    * refactor RS_ZonalStatsAll
    
    * refactor RS_GeoTransform
    
    * modify document
    
    * update the document for RS_GeoTransform
---
 docs/api/sql/Raster-operators.md                   |  70 ++---
 .../expressions/raster/RasterAccessors.scala       |  14 -
 .../expressions/raster/RasterBandAccessors.scala   |  21 --
 .../expressions/raster/RasterFunctions.scala       | 259 ++++++++++++++++
 .../org/apache/sedona/sql/rasteralgebraTest.scala  | 328 ++++++++++++++-------
 5 files changed, 509 insertions(+), 183 deletions(-)

diff --git a/docs/api/sql/Raster-operators.md b/docs/api/sql/Raster-operators.md
index f7dbb719c..08b2d6295 100644
--- a/docs/api/sql/Raster-operators.md
+++ b/docs/api/sql/Raster-operators.md
@@ -452,14 +452,14 @@ SELECT RS_GeoReferrence(ST_MakeEmptyRaster(1, 3, 4, 
100.0, 200.0,2.0, -3.0, 0.1,
 
 ### RS_GeoTransform
 
-Introduction: Returns an array of parameters that represent the 
GeoTransformation of the raster. The array contains the following values:
+Introduction: Returns a struct of parameters that represent the 
GeoTransformation of the raster. The struct has the following schema:
 
-- 0: pixel width along west-east axis (x-axis)
-- 1: pixel height along north-south axis (y-axis)
-- 2: Rotation of the raster
-- 3: Angular separation between x-axis and y-axis
-- 4: X ordinate of upper-left coordinate
-- 5: Y ordinate of upper-left coordinate
+- magnitudeI: size of a pixel along the transformed i axis
+- magnitudeJ: size of a pixel along the transformed j axis
+- thetaI: angle by which the raster is rotated (Radians positive clockwise)
+- thetaIJ: angle from transformed i axis to transformed j axis (Radians 
positive counter-clockwise)
+- offsetX: X ordinate of the upper-left corner of the upper-left pixel
+- offsetY: Y ordinate of the upper-left corner of the upper-left pixel
 
 !!!note
     Refer to [this 
image](https://www.researchgate.net/figure/Relation-between-the-cartesian-axes-x-y-and-i-j-axes-of-the-pixels_fig3_313860913)
 for a clear understanding between i & j axis and x & y-axis.
@@ -479,7 +479,7 @@ SELECT RS_GeoTransform(
 Output:
 
 ```
-[2.23606797749979, 2.23606797749979, -1.1071487177940904, -2.214297435588181, 
1.0, 2.0]
+{2.23606797749979, 2.23606797749979, -1.1071487177940904, -2.214297435588181, 
1.0, 2.0}
 ```
 
 ### RS_Height
@@ -1094,7 +1094,7 @@ Output:
 
 ### RS_SummaryStatsAll
 
-Introduction: Returns summary stats consisting of count, sum, mean, stddev, 
min, max for a given band in raster. If band is not specified then it defaults 
to `1`.
+Introduction: Returns summary stats struct consisting of count, sum, mean, 
stddev, min, max for a given band in raster. If band is not specified then it 
defaults to `1`.
 
 !!!Note
     If excludeNoDataValue is set `true` then it will only count pixels with 
value not equal to the nodata value of the raster.
@@ -1122,7 +1122,7 @@ SELECT RS_SummaryStatsAll(RS_MakeEmptyRaster(2, 5, 5, 0, 
0, 1, -1, 0, 0, 0), 1,
 Output:
 
 ```
-25.0, 204.0, 8.16, 9.4678403028357, 0.0, 25.0
+{25.0, 204.0, 8.16, 9.4678403028357, 0.0, 25.0}
 ```
 
 SQL Example
@@ -1134,7 +1134,7 @@ SELECT RS_SummaryStatsAll(RS_MakeEmptyRaster(2, 5, 5, 0, 
0, 1, -1, 0, 0, 0), 1)
 Output:
 
 ```
-14.0, 204.0, 14.571428571428571, 11.509091348732502, 1.0, 25.0
+{14.0, 204.0, 14.571428571428571, 11.509091348732502, 1.0, 25.0}
 ```
 
 ### RS_ZonalStats
@@ -1207,17 +1207,17 @@ Output:
 
 ### RS_ZonalStatsAll
 
-Introduction: Returns an array of statistic values, where each statistic is 
computed over a region defined by the `zone` geometry. The array contains the 
following values:
+Introduction: Returns a struct of statistic values, where each statistic is 
computed over a region defined by the `zone` geometry. The struct has the 
following schema:
 
-- 0: Count of the pixels.
-- 1: Sum of the pixel values.
-- 2: Arithmetic mean.
-- 3: Median.
-- 4: Mode.
-- 5: Standard deviation.
-- 6: Variance.
-- 7: Minimum value of the zone.
-- 8: Maximum value of the zone.
+- count: Count of the pixels.
+- sum: Sum of the pixel values.
+- mean: Arithmetic mean.
+- median: Median.
+- mode: Mode.
+- stddev: Standard deviation.
+- variance: Variance.
+- min: Minimum value of the zone.
+- max: Maximum value of the zone.
 
 !!!note
     If the coordinate reference system (CRS) of the input `zone` geometry 
differs from that of the `raster`, then `zone` will be transformed to match the 
CRS of the `raster` before computation.
@@ -1258,7 +1258,7 @@ RS_ZonalStatsAll(rast1, geom1, 1, false)
 Output:
 
 ```
-[184792.0, 1.0690406E7, 57.851021689230684, 0.0, 0.0, 92.13277429243035, 
8488.448098819916, 0.0, 255.0]
+{184792.0, 1.0690406E7, 57.851021689230684, 0.0, 0.0, 92.13277429243035, 
8488.448098819916, 0.0, 255.0}
 ```
 
 SQL Example
@@ -1270,7 +1270,7 @@ RS_ZonalStatsAll(rast2, geom2, 1, true)
 Output:
 
 ```
-[14184.0, 3213526.0, 226.55992667794473, 255.0, 255.0, 74.87605357255357, 
5606.423398599913, 1.0, 255.0]
+{14184.0, 3213526.0, 226.55992667794473, 255.0, 255.0, 74.87605357255357, 
5606.423398599913, 1.0, 255.0}
 ```
 
 ## Raster Predicates
@@ -1541,18 +1541,18 @@ Output (Shown as heatmap):
 
 ### RS_MetaData
 
-Introduction: Returns the metadata of the raster as an array of double. The 
array contains the following values:
+Introduction: Returns the metadata of the raster as a struct. The struct has 
the following schema:
 
-- 0: upper left x coordinate of the raster, in terms of CRS units
-- 1: upper left y coordinate of the raster, in terms of CRS units
-- 2: width of the raster, in terms of pixels
-- 3: height of the raster, in terms of pixels
-- 4: ScaleX: the scaling factor in the x direction
-- 5: ScaleY: the scaling factor in the y direction
-- 6: skew in x direction (rotation x)
-- 7: skew in y direction (rotation y)
-- 8: srid of the raster
-- 9: number of bands
+- upperLeftX: upper left x coordinate of the raster, in terms of CRS units
+- upperLeftX: upper left y coordinate of the raster, in terms of CRS units
+- gridWidth: width of the raster, in terms of pixels
+- gridHeight: height of the raster, in terms of pixels
+- scaleX: ScaleX: the scaling factor in the x direction
+- scaleY: ScaleY: the scaling factor in the y direction
+- skewX: skew in x direction (rotation x)
+- skewY: skew in y direction (rotation y)
+- srid: srid of the raster
+- numSampleDimensions: number of bands
 
 For more information about ScaleX, ScaleY, SkewX, SkewY, please refer to the 
[Affine Transformations](Raster-affine-transformation.md) section.
 
@@ -1569,7 +1569,7 @@ SELECT RS_MetaData(raster) FROM raster_table
 Output:
 
 ```
-[-1.3095817809482181E7, 4021262.7487925636, 512.0, 517.0, 72.32861272132695, 
-72.32861272132695, 0.0, 0.0, 3857.0, 1.0]
+{-1.3095817809482181E7, 4021262.7487925636, 512.0, 517.0, 72.32861272132695, 
-72.32861272132695, 0.0, 0.0, 3857.0, 1.0}
 ```
 
 ### RS_NormalizeAll
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
index 350401303..6c4973f08 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
@@ -38,13 +38,6 @@ case class RS_SRID(inputExpressions: Seq[Expression])
   }
 }
 
-case class RS_Metadata(inputExpressions: Seq[Expression])
-    extends InferredExpression(RasterAccessors.metadata _) {
-  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
-    copy(inputExpressions = newChildren)
-  }
-}
-
 case class RS_Width(inputExpressions: Seq[Expression])
     extends InferredExpression(RasterAccessors.getWidth _) {
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
@@ -103,13 +96,6 @@ case class RS_Rotation(inputExpressions: Seq[Expression])
   }
 }
 
-case class RS_GeoTransform(inputExpressions: Seq[Expression])
-    extends InferredExpression(RasterAccessors.getGeoTransform _) {
-  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
-    copy(inputExpressions = newChildren)
-  }
-}
-
 case class RS_SkewX(inputExpressions: Seq[Expression])
     extends InferredExpression(RasterAccessors.getSkewX _) {
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): 
Expression = {
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterBandAccessors.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterBandAccessors.scala
index 1ea82cd5f..3108be64f 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterBandAccessors.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterBandAccessors.scala
@@ -59,17 +59,6 @@ case class RS_ZonalStats(inputExpressions: Seq[Expression])
   }
 }
 
-case class RS_ZonalStatsAll(inputExpressions: Seq[Expression])
-    extends InferredExpression(
-      inferrableFunction2(RasterBandAccessors.getZonalStatsAll),
-      inferrableFunction4(RasterBandAccessors.getZonalStatsAll),
-      inferrableFunction3(RasterBandAccessors.getZonalStatsAll),
-      inferrableFunction5(RasterBandAccessors.getZonalStatsAll)) {
-  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
-    copy(inputExpressions = newChildren)
-  }
-}
-
 case class RS_SummaryStats(inputExpressions: Seq[Expression])
     extends InferredExpression(
       inferrableFunction2(RasterBandAccessors.getSummaryStats),
@@ -80,16 +69,6 @@ case class RS_SummaryStats(inputExpressions: Seq[Expression])
   }
 }
 
-case class RS_SummaryStatsAll(inputExpressions: Seq[Expression])
-    extends InferredExpression(
-      inferrableFunction1(RasterBandAccessors.getSummaryStatsAll),
-      inferrableFunction2(RasterBandAccessors.getSummaryStatsAll),
-      inferrableFunction3(RasterBandAccessors.getSummaryStatsAll)) {
-  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
-    copy(inputExpressions = newChildren)
-  }
-}
-
 case class RS_Band(inputExpressions: Seq[Expression])
     extends InferredExpression(RasterBandAccessors.getBand _) {
 
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterFunctions.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterFunctions.scala
new file mode 100644
index 000000000..971be31ae
--- /dev/null
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterFunctions.scala
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.spark.sql.sedona_sql.expressions.raster
+
+import org.apache.sedona.common.raster.{RasterAccessors, RasterBandAccessors}
+import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.sql.catalyst.expressions.{ExpectsInputTypes, 
Expression}
+import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
+import org.apache.spark.sql.sedona_sql.UDT.{GeometryUDT, RasterUDT}
+import 
org.apache.spark.sql.sedona_sql.expressions.implicits.InputExpressionEnhancer
+import 
org.apache.spark.sql.sedona_sql.expressions.raster.implicits.RasterInputExpressionEnhancer
+import org.apache.spark.sql.types.{AbstractDataType, BooleanType, DataType, 
DoubleType, IntegerType, StructField, StructType}
+
+case class RS_Metadata(inputExpressions: Seq[Expression])
+    extends Expression
+    with CodegenFallback
+    with ExpectsInputTypes {
+
+  override def nullable: Boolean = true
+
+  override def dataType: DataType = StructType(
+    Seq(
+      StructField("upperLeftX", DoubleType, nullable = false),
+      StructField("upperLeftY", DoubleType, nullable = false),
+      StructField("gridWidth", DoubleType, nullable = false),
+      StructField("gridHeight", DoubleType, nullable = false),
+      StructField("scaleX", DoubleType, nullable = false),
+      StructField("scaleY", DoubleType, nullable = false),
+      StructField("skewX", DoubleType, nullable = false),
+      StructField("skewY", DoubleType, nullable = false),
+      StructField("srid", DoubleType, nullable = false),
+      StructField("numSampleDimensions", DoubleType, nullable = false)))
+
+  override def eval(input: InternalRow): Any = {
+    // Evaluate the input expressions
+    val rasterGeom = inputExpressions(0).toRaster(input)
+
+    // Check if the raster geometry is null
+    if (rasterGeom == null) {
+      null
+    } else {
+      // Get the metadata using the Java method
+      val metaData = RasterAccessors.metadata(rasterGeom)
+
+      // Create an InternalRow with the metadata
+      InternalRow.fromSeq(metaData.map(_.asInstanceOf[Any]))
+    }
+  }
+
+  override def children: Seq[Expression] = inputExpressions
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): 
RS_Metadata = {
+    copy(inputExpressions = newChildren)
+  }
+
+  override def inputTypes: Seq[AbstractDataType] = Seq(RasterUDT)
+}
+
+case class RS_SummaryStatsAll(inputExpressions: Seq[Expression])
+    extends Expression
+    with CodegenFallback
+    with ExpectsInputTypes {
+
+  override def nullable: Boolean = true
+
+  override def dataType: DataType = StructType(
+    Seq(
+      StructField("count", DoubleType, nullable = false),
+      StructField("sum", DoubleType, nullable = false),
+      StructField("mean", DoubleType, nullable = false),
+      StructField("stddev", DoubleType, nullable = false),
+      StructField("min", DoubleType, nullable = false),
+      StructField("max", DoubleType, nullable = false)))
+
+  override def eval(input: InternalRow): Any = {
+    // Evaluate the input expressions
+    val rasterGeom = inputExpressions(0).toRaster(input)
+    val band = if (inputExpressions.length >= 2) {
+      inputExpressions(1).eval(input).asInstanceOf[Int]
+    } else {
+      1
+    }
+    val noData = if (inputExpressions.length >= 3) {
+      inputExpressions(2).eval(input).asInstanceOf[Boolean]
+    } else {
+      true
+    }
+
+    // Check if the raster geometry is null
+    if (rasterGeom == null) {
+      null
+    } else {
+      val summaryStatsAll = RasterBandAccessors.getSummaryStatsAll(rasterGeom, 
band, noData)
+
+      if (summaryStatsAll == null) {
+        return null
+      }
+      // Create an InternalRow with the summaryStatsAll
+      InternalRow.fromSeq(summaryStatsAll.map(_.asInstanceOf[Any]))
+    }
+  }
+
+  override def children: Seq[Expression] = inputExpressions
+
+  protected def withNewChildrenInternal(
+      newChildren: IndexedSeq[Expression]): RS_SummaryStatsAll = {
+    copy(inputExpressions = newChildren)
+  }
+
+  override def inputTypes: Seq[AbstractDataType] = {
+    if (inputExpressions.length == 1) {
+      Seq(RasterUDT)
+    } else if (inputExpressions.length == 2) {
+      Seq(RasterUDT, IntegerType)
+    } else if (inputExpressions.length == 3) {
+      Seq(RasterUDT, IntegerType, BooleanType)
+    } else {
+      Seq(RasterUDT)
+    }
+  }
+}
+
+case class RS_ZonalStatsAll(inputExpressions: Seq[Expression])
+    extends Expression
+    with CodegenFallback
+    with ExpectsInputTypes {
+
+  override def nullable: Boolean = true
+
+  override def dataType: DataType = StructType(
+    Seq(
+      StructField("count", DoubleType, nullable = false),
+      StructField("sum", DoubleType, nullable = false),
+      StructField("mean", DoubleType, nullable = false),
+      StructField("median", DoubleType, nullable = false),
+      StructField("mode", DoubleType, nullable = false),
+      StructField("stddev", DoubleType, nullable = false),
+      StructField("variance", DoubleType, nullable = false),
+      StructField("min", DoubleType, nullable = false),
+      StructField("max", DoubleType, nullable = false)))
+
+  override def eval(input: InternalRow): Any = {
+    // Evaluate the input expressions
+    val rasterGeom = inputExpressions(0).toRaster(input)
+    val roi = if (inputExpressions.length >= 2) {
+      inputExpressions(1).toGeometry(input)
+    } else {
+      null
+    }
+    val band = if (inputExpressions.length >= 3) {
+      inputExpressions(2).eval(input).asInstanceOf[Int]
+    } else {
+      1
+    }
+    val noData = if (inputExpressions.length >= 4) {
+      inputExpressions(3).eval(input).asInstanceOf[Boolean]
+    } else {
+      true
+    }
+    val lenient = if (inputExpressions.length >= 5) {
+      inputExpressions(4).eval(input).asInstanceOf[Boolean]
+    } else {
+      true
+    }
+
+    // Check if the raster geometry is null
+    if (rasterGeom == null) {
+      null
+    } else {
+      val zonalStatsAll =
+        RasterBandAccessors.getZonalStatsAll(rasterGeom, roi, band, noData, 
lenient)
+      // Create an InternalRow with the zonalStatsAll
+      if (zonalStatsAll == null) {
+        return null
+      }
+      InternalRow.fromSeq(zonalStatsAll.map(_.asInstanceOf[Any]))
+    }
+  }
+
+  override def children: Seq[Expression] = inputExpressions
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): 
RS_ZonalStatsAll = {
+    copy(inputExpressions = newChildren)
+  }
+
+  override def inputTypes: Seq[AbstractDataType] = {
+    if (inputExpressions.length == 2) {
+      Seq(RasterUDT, GeometryUDT)
+    } else if (inputExpressions.length == 3) {
+      Seq(RasterUDT, GeometryUDT, IntegerType)
+    } else if (inputExpressions.length == 4) {
+      Seq(RasterUDT, GeometryUDT, IntegerType, BooleanType)
+    } else if (inputExpressions.length >= 5) {
+      Seq(RasterUDT, GeometryUDT, IntegerType, BooleanType)
+    } else {
+      Seq(RasterUDT, GeometryUDT)
+    }
+  }
+}
+
+case class RS_GeoTransform(inputExpressions: Seq[Expression])
+    extends Expression
+    with CodegenFallback
+    with ExpectsInputTypes {
+
+  override def nullable: Boolean = true
+
+  override def dataType: DataType = StructType(
+    Seq(
+      StructField("magnitudeI", DoubleType, nullable = false),
+      StructField("magnitudeJ", DoubleType, nullable = false),
+      StructField("thetaI", DoubleType, nullable = false),
+      StructField("thetaIJ", DoubleType, nullable = false),
+      StructField("offsetX", DoubleType, nullable = false),
+      StructField("offsetY", DoubleType, nullable = false)))
+
+  override def eval(input: InternalRow): Any = {
+    // Evaluate the input expressions
+    val rasterGeom = inputExpressions(0).toRaster(input)
+
+    // Check if the raster geometry is null
+    if (rasterGeom == null) {
+      null
+    } else {
+      // Get the metadata using the Java method
+      val geoTransform = RasterAccessors.getGeoTransform(rasterGeom)
+
+      if (geoTransform == null) {
+        return null
+      }
+      // Create an InternalRow with the metadata
+      InternalRow.fromSeq(geoTransform.map(_.asInstanceOf[Any]))
+    }
+  }
+
+  override def children: Seq[Expression] = inputExpressions
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): 
RS_GeoTransform = {
+    copy(inputExpressions = newChildren)
+  }
+
+  override def inputTypes: Seq[AbstractDataType] = Seq(RasterUDT)
+}
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
index ba6f8d266..3343ae64e 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
@@ -21,8 +21,9 @@ package org.apache.sedona.sql
 import org.apache.sedona.common.raster.MapAlgebra
 import org.apache.sedona.common.utils.RasterUtils
 import org.apache.spark.sql.expressions.Window
-import org.apache.spark.sql.{Row, SaveMode}
-import org.apache.spark.sql.functions.{col, collect_list, expr, lit, 
monotonically_increasing_id, row_number}
+import org.apache.spark.sql.{DataFrame, Row, SaveMode}
+import org.apache.spark.sql.functions.{col, collect_list, expr, lit, 
row_number}
+import org.apache.spark.sql.types.{DoubleType, StructField, StructType}
 import org.geotools.coverage.grid.GridCoverage2D
 import org.junit.Assert.{assertEquals, assertNotNull, assertNull, assertTrue}
 import org.locationtech.jts.geom.{Coordinate, Geometry}
@@ -432,18 +433,41 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         "RS_Metadata(rast_4326) as metadata_4326")
       val result = dfResult.first()
       assert(result.getInt(0) == 4326)
-      val metadata = result.getSeq[Double](1)
-      val metadata4326 = result.getSeq[Double](2)
-      assert(metadata4326(8) == 4326)
-      assert(metadata(0) == metadata4326(0))
-      assert(metadata(1) == metadata4326(1))
-      assert(metadata(2) == metadata4326(2))
-      assert(metadata(3) == metadata4326(3))
-      assert(metadata(4) == metadata4326(4))
-      assert(metadata(5) == metadata4326(5))
-      assert(metadata(6) == metadata4326(6))
-      assert(metadata(7) == metadata4326(7))
-      assert(metadata(9) == metadata4326(9))
+
+      // Assert SRID
+      assert(result.getInt(0) == 4326)
+
+      // Extract metadata and metadata_4326
+      val metadata = result.getStruct(1)
+      val metadata_4326 = result.getStruct(2)
+
+      // Assert metadata schema
+      val expectedSchema = StructType(
+        Seq(
+          StructField("upperLeftX", DoubleType, nullable = false),
+          StructField("upperLeftY", DoubleType, nullable = false),
+          StructField("gridWidth", DoubleType, nullable = false),
+          StructField("gridHeight", DoubleType, nullable = false),
+          StructField("scaleX", DoubleType, nullable = false),
+          StructField("scaleY", DoubleType, nullable = false),
+          StructField("skewX", DoubleType, nullable = false),
+          StructField("skewY", DoubleType, nullable = false),
+          StructField("srid", DoubleType, nullable = false),
+          StructField("numSampleDimensions", DoubleType, nullable = false)))
+
+      assert(dfResult.schema("metadata").dataType == expectedSchema)
+      assert(dfResult.schema("metadata_4326").dataType == expectedSchema)
+
+      // Assert metadata fields
+      assert(metadata.getDouble(0) == metadata_4326.getDouble(0))
+      assert(metadata.getDouble(1) == metadata_4326.getDouble(1))
+      assert(metadata.getDouble(2) == metadata_4326.getDouble(2))
+      assert(metadata.getDouble(3) == metadata_4326.getDouble(3))
+      assert(metadata.getDouble(4) == metadata_4326.getDouble(4))
+      assert(metadata.getDouble(5) == metadata_4326.getDouble(5))
+      assert(metadata.getDouble(6) == metadata_4326.getDouble(6))
+      assert(metadata.getDouble(7) == metadata_4326.getDouble(7))
+      assert(metadata.getDouble(9) == metadata_4326.getDouble(9))
     }
 
     it("Passed RS_SetGeoReference should handle null values") {
@@ -517,11 +541,11 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       val expected = 3
       assertEquals(expected, actual)
 
-      val actualMetadata =
-        resultDf.selectExpr("RS_Metadata(raster)").first().getSeq(0).slice(0, 
9)
-      val expectedMetadata =
-        df.selectExpr("RS_Metadata(emptyRaster)").first().getSeq(0).slice(0, 9)
-      assertEquals(expectedMetadata.toString(), actualMetadata.toString())
+      assertRSMetadata(
+        df,
+        "RS_Metadata(emptyRaster) as metadata",
+        resultDf,
+        "RS_Metadata(raster) as metadata")
 
       val actualBandValues = resultDf.selectExpr("RS_BandAsArray(raster, 
1)").first().getSeq(0)
       val expectedBandValues = df.selectExpr("RS_BandAsArray(emptyRaster, 
1)").first().getSeq(0)
@@ -538,10 +562,11 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       val expected = 5
       assertEquals(expected, actual)
 
-      val actualMetadata =
-        
resultDf.selectExpr("RS_Metadata(resultRaster)").first().getSeq(0).slice(0, 9)
-      val expectedMetadata = 
df.selectExpr("RS_Metadata(raster)").first().getSeq(0).slice(0, 9)
-      assertEquals(expectedMetadata.toString(), actualMetadata.toString())
+      assertRSMetadata(
+        resultDf,
+        "RS_Metadata(resultRaster) as metadata",
+        df,
+        "RS_Metadata(raster) as metadata")
     }
 
     it("Passed RS_Union") {
@@ -570,10 +595,11 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       var expectedBandValues = df.selectExpr("RS_BandAsArray(raster2, 
1)").first().getSeq(0)
       assertTrue(expectedBandValues.equals(actualBandValues))
 
-      var actualMetadata =
-        
actualDf.selectExpr("RS_Metadata(actualRaster)").first().getSeq(0).slice(0, 9)
-      var expectedMetadata = 
df.selectExpr("RS_Metadata(raster1)").first().getSeq(0).slice(0, 9)
-      assertTrue(expectedMetadata.equals(actualMetadata))
+      assertRSMetadata(
+        actualDf,
+        "RS_Metadata(actualRaster) as metadata",
+        df,
+        "RS_Metadata(raster1) as metadata")
 
       actualDf = df.selectExpr("RS_Union(raster1, raster2, raster3) as 
actualRaster")
 
@@ -588,10 +614,11 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       expectedBandValues = df.selectExpr("RS_BandAsArray(raster3, 
1)").first().getSeq(0)
       assertTrue(expectedBandValues.equals(actualBandValues))
 
-      actualMetadata =
-        
actualDf.selectExpr("RS_Metadata(actualRaster)").first().getSeq(0).slice(0, 9)
-      expectedMetadata = 
df.selectExpr("RS_Metadata(raster1)").first().getSeq(0).slice(0, 9)
-      assertTrue(expectedMetadata.equals(actualMetadata))
+      assertRSMetadata(
+        actualDf,
+        "RS_Metadata(actualRaster) as metadata",
+        df,
+        "RS_Metadata(raster1) as metadata")
 
       actualDf = df.selectExpr(
         "RS_Union(raster1, raster2, raster3, raster1, raster2, raster3, 
raster1) as actualRaster")
@@ -607,10 +634,11 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       expectedBandValues = df.selectExpr("RS_BandAsArray(raster3, 
2)").first().getSeq(0)
       assertTrue(expectedBandValues.equals(actualBandValues))
 
-      actualMetadata =
-        
actualDf.selectExpr("RS_Metadata(actualRaster)").first().getSeq(0).slice(0, 9)
-      expectedMetadata = 
df.selectExpr("RS_Metadata(raster1)").first().getSeq(0).slice(0, 9)
-      assertTrue(expectedMetadata.equals(actualMetadata))
+      assertRSMetadata(
+        actualDf,
+        "RS_Metadata(actualRaster) as metadata",
+        df,
+        "RS_Metadata(raster1) as metadata")
     }
 
     it("Passed RS_AddBand with empty raster") {
@@ -637,10 +665,11 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       val expectedBandValues = df.selectExpr("RS_BandAsArray(fromRaster, 
1)").first().getSeq(0)
       assertTrue(expectedBandValues.equals(actualBandValues))
 
-      val actualMetadata =
-        
actualDf.selectExpr("RS_Metadata(actualRaster)").first().getSeq(0).slice(0, 9)
-      val expectedMetadata = 
df.selectExpr("RS_Metadata(toRaster)").first().getSeq(0).slice(0, 9)
-      assertTrue(expectedMetadata.equals(actualMetadata))
+      assertRSMetadata(
+        df,
+        "RS_Metadata(toRaster) as metadata",
+        actualDf,
+        "RS_Metadata(actualRaster) as metadata")
     }
 
     it("Passed RS_SetValues with raster and implicit CRS transformation") {
@@ -926,12 +955,32 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
           "ST_GeomFromWKT('POLYGON ((-8682522.873537656 4572703.890837922, 
-8673439.664183248 4572993.532747675, -8673155.57366801 4563873.2099182755, 
-8701890.325907696 4562931.7093397, -8682522.873537656 4572703.890837922))', 
3857) as geom")
       val clippedDf = df.selectExpr("RS_Clip(raster, 1, geom, 200, false) as 
clipped")
 
-      val clippedMetadata = 
df.selectExpr("RS_Metadata(raster)").first().getSeq(0).slice(0, 9)
-      val originalMetadata =
-        
clippedDf.selectExpr("RS_Metadata(clipped)").first().getSeq(0).slice(0, 9)
-      assertTrue(originalMetadata.equals(clippedMetadata))
+      // Extract the metadata for the original raster
+      val clippedMetadata = df
+        .selectExpr("RS_Metadata(raster) as metadata")
+        .first()
+        .getStruct(0)
 
-      var actualValues = clippedDf
+      // Extract the metadata for the clipped raster
+      val originalMetadata = clippedDf
+        .selectExpr("RS_Metadata(clipped) as metadata")
+        .first()
+        .getStruct(0)
+
+      // Convert Struct to Seq[Double]
+      def structToSeq(struct: Row): Seq[Double] = {
+        (0 until struct.size).map(struct.getDouble)
+      }
+
+      // Compare the metadata
+      val clippedMetadataSeq = structToSeq(clippedMetadata).slice(0, 9)
+      val originalMetadataSeq = structToSeq(originalMetadata).slice(0, 9)
+      assert(
+        clippedMetadataSeq == originalMetadataSeq,
+        s"Expected: $originalMetadataSeq, but got: $clippedMetadataSeq")
+
+      // Extract the RS_Values for the specified points
+      val actualValues = clippedDf
         .selectExpr(
           "RS_Values(clipped, " +
             "Array(ST_GeomFromWKT('POINT(223802 4.21769e+06)', 
26918),ST_GeomFromWKT('POINT(224759 4.20453e+06)', 26918)," +
@@ -952,9 +1001,10 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
           "ST_GeomFromWKT('POLYGON ((236722 4204770, 243900 4204770, 243900 
4197590, 221170 4197590, 236722 4204770))', 26918) as geom")
       val clippedDf = df.selectExpr("RS_Clip(raster, 1, geom, 200, false) as 
clipped")
 
-      val clippedMetadata = 
df.selectExpr("RS_Metadata(raster)").first().getSeq(0).slice(0, 9)
+      val clippedMetadata =
+        
df.selectExpr("RS_Metadata(raster)").first().getStruct(0).toSeq.slice(0, 9)
       val originalMetadata =
-        
clippedDf.selectExpr("RS_Metadata(clipped)").first().getSeq(0).slice(0, 9)
+        
clippedDf.selectExpr("RS_Metadata(clipped)").first().getStruct(0).toSeq.slice(0,
 9)
       assertTrue(originalMetadata.equals(clippedMetadata))
 
       var actualValues = clippedDf
@@ -1112,7 +1162,7 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
     it("Passed RS_GeoTransform") {
       val df =
         sparkSession.sql("SELECT RS_MakeEmptyRaster(2, 10, 15, 1, 2, 1, -2, 1, 
2, 0) as raster")
-      val actual = df.selectExpr("RS_GeoTransform(raster)").first().getSeq(0)
+      val actual = 
df.selectExpr("RS_GeoTransform(raster)").first().getStruct(0).toSeq.slice(0, 6)
       val expected = mutable.ArraySeq(2.23606797749979, 2.23606797749979, 
-1.1071487177940904,
         -2.214297435588181, 1.0, 2.0)
       assertTrue(expected.equals(actual))
@@ -1134,11 +1184,24 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
 
     it("Passed RS_Metadata") {
       val df = sparkSession.read.format("binaryFile").load(resourceFolder + 
"raster/test1.tiff")
-      val result = 
df.selectExpr("RS_Metadata(RS_FromGeoTiff(content))").first().getSeq(0)
-      assertEquals(10, result.length)
-      assertEquals(512.0, result(2), 0.001)
-      assertEquals(517.0, result(3), 0.001)
-      assertEquals(1.0, result(9), 0.001)
+
+      // Extract the metadata struct
+      val result =
+        df.selectExpr("RS_Metadata(RS_FromGeoTiff(content)) as 
metadata").first().getStruct(0)
+
+      // Function to convert Struct to Seq[Double]
+      def structToSeq(struct: Row): Seq[Double] = {
+        (0 until struct.size).map(struct.getDouble)
+      }
+
+      // Convert the struct to a sequence of doubles
+      val metadataSeq = structToSeq(result)
+
+      // Assertions
+      assert(metadataSeq.length == 10)
+      assert(metadataSeq(2) == 512.0)
+      assert(metadataSeq(3) == 517.0)
+      assert(metadataSeq(9) == 1.0)
     }
 
     it("Passed RS_MakeEmptyRaster") {
@@ -1153,24 +1216,24 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         .sql(
           s"SELECT RS_Metadata(RS_MakeEmptyRaster($numBands, $widthInPixel, 
$heightInPixel, $upperLeftX, $upperLeftY, $cellSize))")
         .first()
-        .getSeq(0)
-      assertEquals(numBands, result(9), 0.001)
+        .getStruct(0)
+      assertEquals(numBands, result.getDouble(9), 0.001)
 
       // Test without skewX, skewY, srid
       result = sparkSession
         .sql(
           s"SELECT RS_Metadata(RS_MakeEmptyRaster($numBands, 'I', 
$widthInPixel, $heightInPixel, $upperLeftX, $upperLeftY, $cellSize))")
         .first()
-        .getSeq(0)
-      assertEquals(numBands, result(9), 0.001)
+        .getStruct(0)
+      assertEquals(numBands, result.getDouble(9), 0.001)
 
       // Test with integer type input
       result = sparkSession
         .sql(
           s"SELECT RS_Metadata(RS_MakeEmptyRaster($numBands, $widthInPixel, 
$heightInPixel, ${upperLeftX.toInt}, ${upperLeftY.toInt}, ${cellSize.toInt}))")
         .first()
-        .getSeq(0)
-      assertEquals(numBands, result(9), 0.001)
+        .getStruct(0)
+      assertEquals(numBands, result.getDouble(9), 0.001)
 
       // Test with skewX, skewY, srid but WITHOUT datatype
       val skewX = 0.0
@@ -1180,16 +1243,16 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         .sql(
           s"SELECT RS_Metadata(RS_MakeEmptyRaster($numBands, $widthInPixel, 
$heightInPixel, $upperLeftX, $upperLeftY, $cellSize, -$cellSize, $skewX, 
$skewY, $srid))")
         .first()
-        .getSeq(0)
-      assertEquals(numBands, result(9), 0.001)
+        .getStruct(0)
+      assertEquals(numBands, result.getDouble(9), 0.001)
 
       // Test with skewX, skewY, srid and datatype
       result = sparkSession
         .sql(
           s"SELECT RS_Metadata(RS_MakeEmptyRaster($numBands, 'I', 
$widthInPixel, $heightInPixel, $upperLeftX, $upperLeftY, $cellSize, -$cellSize, 
$skewX, $skewY, $srid))")
         .first()
-        .getSeq(0)
-      assertEquals(numBands, result(9), 0.001)
+        .getStruct(0)
+      assertEquals(numBands, result.getDouble(9), 0.001)
     }
 
     it("Passed RS_MakeRaster") {
@@ -1197,7 +1260,7 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         .format("binaryFile")
         .load(resourceFolder + "raster/test1.tiff")
         .withColumn("rast", expr("RS_FromGeoTiff(content)"))
-      val metadata = df.selectExpr("RS_Metadata(rast)").first().getSeq(0)
+      val metadata = 
df.selectExpr("RS_Metadata(rast)").first().getStruct(0).toSeq
       val width = metadata(2).asInstanceOf[Double].toInt
       val height = metadata(3).asInstanceOf[Double].toInt
       val values = Array.tabulate(width * height) { i => i * i }
@@ -1228,7 +1291,8 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
 
     it("Passed RS_BandAsArray") {
       val df = sparkSession.read.format("binaryFile").load(resourceFolder + 
"raster/test1.tiff")
-      val metadata = 
df.selectExpr("RS_Metadata(RS_FromGeoTiff(content))").first().getSeq(0)
+      val metadata =
+        
df.selectExpr("RS_Metadata(RS_FromGeoTiff(content))").first().getStruct(0).toSeq
       val width = metadata(2).asInstanceOf[Double].toInt
       val height = metadata(3).asInstanceOf[Double].toInt
       val result = df.selectExpr("RS_BandAsArray(RS_FromGeoTiff(content), 
1)").first().getSeq(0)
@@ -1464,9 +1528,10 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       val expectedBands = 3
       assertEquals(expectedBands, actualBands)
 
-      val actualMetadata = 
df.selectExpr("RS_Metadata(rasters)").first().getSeq(0).slice(0, 9)
+      val actualMetadata =
+        
df.selectExpr("RS_Metadata(rasters)").first().getStruct(0).toSeq.slice(0, 9)
       val expectedMetadata =
-        dfTest.selectExpr("RS_Metadata(raster)").first().getSeq(0).slice(0, 9)
+        
dfTest.selectExpr("RS_Metadata(raster)").first().getStruct(0).toSeq.slice(0, 9)
       assertTrue(expectedMetadata.equals(actualMetadata))
     }
 
@@ -1528,20 +1593,21 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       val rowsExpected = df.selectExpr("summary").collect()
       val rowsActual = df.selectExpr("summary").collect()
 
-      val expectedMetadata = df.selectExpr("meta").first().getSeq(0).slice(0, 
9)
-      val expectedSummary1 = rowsExpected(0).getSeq(0).slice(0, 6)
-      val expectedSummary2 = rowsExpected(1).getSeq(0).slice(0, 6)
-      val expectedSummary3 = rowsExpected(2).getSeq(0).slice(0, 6)
-      val expectedSummary4 = rowsExpected(3).getSeq(0).slice(0, 6)
+      val expectedMetadata = 
df.selectExpr("meta").first().getStruct(0).toSeq.slice(0, 9)
+      val expectedSummary1 = rowsExpected(0).getStruct(0).toSeq.slice(0, 6)
+      val expectedSummary2 = rowsExpected(1).getStruct(0).toSeq.slice(0, 6)
+      val expectedSummary3 = rowsExpected(2).getStruct(0).toSeq.slice(0, 6)
+      val expectedSummary4 = rowsExpected(3).getStruct(0).toSeq.slice(0, 6)
 
       val expectedNumBands = mutable.WrappedArray.make(Array(4.0))
 
-      val actualMetadata = 
aggregatedDF2.selectExpr("meta").first().getSeq(0).slice(0, 9)
-      val actualNumBands = 
aggregatedDF2.selectExpr("meta").first().getSeq(0).slice(9, 10)
-      val actualSummary1 = rowsActual(0).getSeq(0).slice(0, 6)
-      val actualSummary2 = rowsActual(1).getSeq(0).slice(0, 6)
-      val actualSummary3 = rowsActual(2).getSeq(0).slice(0, 6)
-      val actualSummary4 = rowsActual(3).getSeq(0).slice(0, 6)
+      val actualMetadata = 
aggregatedDF2.selectExpr("meta").first().getStruct(0).toSeq.slice(0, 9)
+      val actualNumBands =
+        aggregatedDF2.selectExpr("meta").first().getStruct(0).toSeq.slice(9, 
10)
+      val actualSummary1 = rowsActual(0).getStruct(0).toSeq.slice(0, 6)
+      val actualSummary2 = rowsActual(1).getStruct(0).toSeq.slice(0, 6)
+      val actualSummary3 = rowsActual(2).getStruct(0).toSeq.slice(0, 6)
+      val actualSummary4 = rowsActual(3).getStruct(0).toSeq.slice(0, 6)
 
       assertTrue(expectedMetadata.equals(actualMetadata))
       assertTrue(actualNumBands == expectedNumBands)
@@ -1620,7 +1686,12 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       df = df.selectExpr(
         "RS_FromGeoTiff(content) as raster",
         "ST_GeomFromWKT('POLYGON ((-8673439.6642 4572993.5327, -8673155.5737 
4563873.2099, -8701890.3259 4562931.7093, -8682522.8735 4572703.8908, 
-8673439.6642 4572993.5327))', 3857) as geom")
-      val actual = df.selectExpr("RS_ZonalStatsAll(raster, geom, 1, 
true)").first().get(0)
+      val actual = df
+        .selectExpr("RS_ZonalStatsAll(raster, geom, 1, true)")
+        .first()
+        .getStruct(0)
+        .toSeq
+        .slice(0, 9)
       val expected = Seq(184792.0, 1.0719726e7, 58.00968656653401, 0.0, 0.0, 
92.15004748703687,
         8491.631251863151, 0.0, 255.0)
       assertTrue(expected.equals(actual))
@@ -1630,7 +1701,7 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         .selectExpr(
           "RS_ZonalStatsAll(raster, ST_GeomFromWKT('POLYGON 
((-78.22106647832458748 37.76411511479908967, -78.20183062098976734 
37.72863564460374874, -78.18088490966962922 37.76753482276972562, 
-78.22106647832458748 37.76411511479908967))'), 1, false)")
         .first()
-        .get(0)
+        .getStruct(0)
       assertNull(actual2)
     }
 
@@ -1641,7 +1712,12 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       df = df.selectExpr(
         "RS_FromGeoTiff(content) as raster",
         "ST_GeomFromWKT('POLYGON((-167.750000 87.750000, -155.250000 
87.750000, -155.250000 40.250000, -180.250000 40.250000, -167.750000 
87.750000))', 0) as geom")
-      val actual = df.selectExpr("RS_ZonalStatsAll(raster, geom, 1, 
true)").first().get(0)
+      val actual = df
+        .selectExpr("RS_ZonalStatsAll(raster, geom, 1, true)")
+        .first()
+        .getStruct(0)
+        .toSeq
+        .slice(0, 9)
       val expected = Seq(14184.0, 3213526.0, 226.55992667794473, 255.0, 255.0, 
74.87605357255357,
         5606.423398599913, 1.0, 255.0)
       assertTrue(expected.equals(actual))
@@ -1677,29 +1753,29 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         .format("binaryFile")
         .load(resourceFolder + "raster/raster_with_no_data/test5.tiff")
       df = df.selectExpr("RS_FromGeoTiff(content) as raster")
-      var actual = df.selectExpr("RS_SummaryStatsAll(raster, 1, 
false)").first().getSeq(0)
-      assertEquals(1036800.0, actual.head, 0.1d)
-      assertEquals(2.06233487e8, actual(1), 0.1d)
-      assertEquals(198.91347125771605, actual(2), 1e-6d)
-      assertEquals(95.09054096106192, actual(3), 1e-6d)
-      assertEquals(0.0, actual(4), 0.1d)
-      assertEquals(255.0, actual(5), 0.1d)
-
-      actual = df.selectExpr("RS_SummaryStatsAll(raster, 1)").first().getSeq(0)
-      assertEquals(928192.0, actual.head, 0.1d)
-      assertEquals(2.06233487e8, actual(1), 0.1d)
-      assertEquals(222.18839097945252, actual(2), 1e-6d)
-      assertEquals(70.20559521132097, actual(3), 1e-6d)
-      assertEquals(1.0, actual(4), 0.1d)
-      assertEquals(255.0, actual(5), 0.1d)
-
-      actual = df.selectExpr("RS_SummaryStatsAll(raster)").first().getSeq(0)
-      assertEquals(928192.0, actual.head, 0.1d)
-      assertEquals(2.06233487e8, actual(1), 0.1d)
-      assertEquals(222.18839097945252, actual(2), 1e-6d)
-      assertEquals(70.20559521132097, actual(3), 1e-6d)
-      assertEquals(1.0, actual(4), 0.1d)
-      assertEquals(255.0, actual(5), 0.1d)
+      var actual = df.selectExpr("RS_SummaryStatsAll(raster, 1, 
false)").first().getStruct(0)
+      assertEquals(1036800.0, actual.getDouble(0), 0.1d)
+      assertEquals(2.06233487e8, actual.getDouble(1), 0.1d)
+      assertEquals(198.91347125771605, actual.getDouble(2), 1e-6d)
+      assertEquals(95.09054096106192, actual.getDouble(3), 1e-6d)
+      assertEquals(0.0, actual.getDouble(4), 0.1d)
+      assertEquals(255.0, actual.getDouble(5), 0.1d)
+
+      actual = df.selectExpr("RS_SummaryStatsAll(raster, 
1)").first().getStruct(0)
+      assertEquals(928192.0, actual.getDouble(0), 0.1d)
+      assertEquals(2.06233487e8, actual.getDouble(1), 0.1d)
+      assertEquals(222.18839097945252, actual.getDouble(2), 1e-6d)
+      assertEquals(70.20559521132097, actual.getDouble(3), 1e-6d)
+      assertEquals(1.0, actual.getDouble(4), 0.1d)
+      assertEquals(255.0, actual.getDouble(5), 0.1d)
+
+      actual = df.selectExpr("RS_SummaryStatsAll(raster)").first().getStruct(0)
+      assertEquals(928192.0, actual.getDouble(0), 0.1d)
+      assertEquals(2.06233487e8, actual.getDouble(1), 0.1d)
+      assertEquals(222.18839097945252, actual.getDouble(2), 1e-6d)
+      assertEquals(70.20559521132097, actual.getDouble(3), 1e-6d)
+      assertEquals(1.0, actual.getDouble(4), 0.1d)
+      assertEquals(255.0, actual.getDouble(5), 0.1d)
     }
 
     it("Passed RS_BandIsNoData") {
@@ -2428,14 +2504,14 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         "RS_AddBandFromArray(RS_MakeEmptyRaster(1, 'd', 4, 3, 0, 0, 2, -2, 0, 
0, 0), band, 1, null) as raster")
       val rasterDf = df.selectExpr("RS_Resample(raster, 6, 5, 1, -1, false, 
null) as raster")
       val rasterOutput = 
rasterDf.selectExpr("RS_AsMatrix(raster)").first().getString(0)
-      val rasterMetadata = 
rasterDf.selectExpr("RS_Metadata(raster)").first().getSeq[Double](0)
+      val rasterMetadata = 
rasterDf.selectExpr("RS_Metadata(raster)").first().getStruct(0)
       val expectedOutput =
         "| 1.0   1.0   2.0   3.0   3.0   5.0|\n" + "| 1.0   1.0   2.0   3.0   
3.0   5.0|\n" + "| 4.0   4.0   5.0   6.0   6.0   9.0|\n" + "| 7.0   7.0   8.0   
9.0   9.0  10.0|\n" + "| 7.0   7.0   8.0   9.0   9.0  10.0|\n"
       val expectedMetadata: Seq[Double] =
         Seq(-0.33333333333333326, 0.19999999999999996, 6, 5, 
1.388888888888889, -1.24, 0, 0, 0, 1)
       assertEquals(expectedOutput, rasterOutput)
       for (i <- expectedMetadata.indices) {
-        assertEquals(expectedMetadata(i), rasterMetadata(i), 1e-6)
+        assertEquals(expectedMetadata(i), rasterMetadata.getDouble(i), 1e-6)
       }
     }
 
@@ -2445,13 +2521,13 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         "RS_AddBandFromArray(RS_MakeEmptyRaster(1, 'd', 4, 3, 0, 0, 2, -2, 0, 
0, 0), band, 1, null) as raster")
       val rasterDf = df.selectExpr("RS_Resample(raster, 1.2, -1.4, true, null) 
as raster")
       val rasterOutput = 
rasterDf.selectExpr("RS_AsMatrix(raster)").first().getString(0)
-      val rasterMetadata = 
rasterDf.selectExpr("RS_Metadata(raster)").first().getSeq[Double](0)
+      val rasterMetadata = 
rasterDf.selectExpr("RS_Metadata(raster)").first().getStruct(0)
       val expectedOutput =
         "|  1.0    1.0    2.0    3.0    3.0    5.0    5.0|\n" + "|  4.0    4.0 
   5.0    6.0    6.0    9.0    9.0|\n" + "|  4.0    4.0    5.0    6.0    6.0    
9.0    9.0|\n" + "|  7.0    7.0    8.0    9.0    9.0   10.0   10.0|\n" + "|  
NaN    NaN    NaN    NaN    NaN    NaN    NaN|\n"
       val expectedMetadata: Seq[Double] = Seq(0, 0, 7, 5, 1.2, -1.4, 0, 0, 0, 
1)
       assertEquals(expectedOutput, rasterOutput)
       for (i <- expectedMetadata.indices) {
-        assertEquals(expectedMetadata(i), rasterMetadata(i), 1e-6)
+        assertEquals(expectedMetadata(i), rasterMetadata.getDouble(i), 1e-6)
       }
     }
 
@@ -2462,14 +2538,14 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         "RS_MakeEmptyRaster(2, 'd', 6, 5, 1, -1, 1.2, -1.4, 0, 0, 0) as 
refRaster")
       val rasterDf = df.selectExpr("RS_Resample(raster, refRaster, true, null) 
as raster")
       val rasterOutput = 
rasterDf.selectExpr("RS_AsMatrix(raster)").first().getString(0)
-      val rasterMetadata = 
rasterDf.selectExpr("RS_Metadata(raster)").first().getSeq[Double](0)
+      val rasterMetadata = 
rasterDf.selectExpr("RS_Metadata(raster)").first().getStruct(0)
       val expectedOutput =
         "| 1.0   1.0   2.0   3.0   3.0   5.0   5.0|\n" + "| 1.0   1.0   2.0   
3.0   3.0   5.0   5.0|\n" + "| 4.0   4.0   5.0   6.0   6.0   9.0   9.0|\n" + "| 
7.0   7.0   8.0   9.0   9.0  10.0  10.0|\n" + "| 7.0   7.0   8.0   9.0   9.0  
10.0  10.0|\n"
       val expectedMetadata: Seq[Double] =
         Seq(-0.20000000298023224, 0.4000000059604645, 7.0, 5.0, 1.2, -1.4, 
0.0, 0.0, 0.0, 1.0)
       assertEquals(expectedOutput, rasterOutput)
       for (i <- expectedMetadata.indices) {
-        assertEquals(expectedMetadata(i), rasterMetadata(i), 1e-6)
+        assertEquals(expectedMetadata(i), rasterMetadata.getDouble(i), 1e-6)
       }
     }
 
@@ -2500,10 +2576,10 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       val rasterDf = df.selectExpr("RS_FromNetCDF(content, 'O3') as raster")
       val expectedMetadata = Seq(4.9375, 50.9375, 80, 48, 0.125, -0.125, 0, 0, 
0, 4)
       val actualMetadata =
-        rasterDf.selectExpr("RS_Metadata(raster) as 
metadata").first().getSeq[Double](0)
+        rasterDf.selectExpr("RS_Metadata(raster) as 
metadata").first().getStruct(0)
 
       for (i <- expectedMetadata.indices) {
-        assertEquals(expectedMetadata(i), actualMetadata(i), 1e-6)
+        assertEquals(expectedMetadata(i), actualMetadata.getDouble(i), 1e-6)
       }
 
       val expectedFirstVal = 60.95357131958008
@@ -2518,10 +2594,10 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       val rasterDf = df.selectExpr("RS_FromNetCDF(content, 'O3', 'lon', 'lat') 
as raster")
       val expectedMetadata = Seq(4.9375, 50.9375, 80, 48, 0.125, -0.125, 0, 0, 
0, 4)
       val actualMetadata =
-        rasterDf.selectExpr("RS_Metadata(raster) as 
metadata").first().getSeq[Double](0)
+        rasterDf.selectExpr("RS_Metadata(raster) as 
metadata").first().getStruct(0)
 
       for (i <- expectedMetadata.indices) {
-        assertEquals(expectedMetadata(i), actualMetadata(i), 1e-6)
+        assertEquals(expectedMetadata(i), actualMetadata.getDouble(i), 1e-6)
       }
 
       val expectedFirstVal = 60.95357131958008
@@ -2540,4 +2616,30 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       assertEquals(expectedRecordInfo, recordInfo)
     }
   }
+
+  private def assertRSMetadata(
+      expectedDf: DataFrame,
+      expectedCol: String,
+      actualDf: DataFrame,
+      actualCol: String) = {
+    // Extract the actual metadata struct
+    val actualMetadataStruct = 
actualDf.selectExpr(actualCol).first().getStruct(0)
+
+    // Extract the expected metadata struct
+    val expectedMetadataStruct = 
expectedDf.selectExpr(expectedCol).first().getStruct(0)
+
+    // Function to convert Struct to Seq[Double]
+    def structToSeq(struct: Row): Seq[Double] = {
+      (0 until struct.size).map(struct.getDouble)
+    }
+
+    // Convert Structs to Seqs
+    val actualMetadata = structToSeq(actualMetadataStruct).slice(0, 9)
+    val expectedMetadata = structToSeq(expectedMetadataStruct).slice(0, 9)
+
+    // Compare the actual and expected metadata
+    assert(
+      actualMetadata == expectedMetadata,
+      s"Expected: $expectedMetadata, but got: $actualMetadata")
+  }
 }

Reply via email to